Skip to content

Outbound Webhooks

Outbound Webhooks push Whatomate events to your own HTTP endpoints in real time. When a subscribed event fires — a message arrives, a contact is created, a chat is transferred to a human — Whatomate POSTs a signed JSON payload to every webhook that subscribed to it.

Manage webhooks under Settings → Webhooks, or via the API:

  • GET /api/webhooks — list webhooks (also returns the available_events catalog).
  • POST /api/webhooks — create a webhook.
  • GET /api/webhooks/{id} / PUT /api/webhooks/{id} / DELETE /api/webhooks/{id}
  • POST /api/webhooks/{id}/test — send a synchronous test event.

A webhook has a name, a target url, a list of events, optional custom headers, a signing secret, and an is_active flag. At least one event must be selected. The secret is never returned in API responses — the payload exposes only a has_secret boolean.

Seven event types can be subscribed to:

EventFires when
message.incomingA new message is received from a contact.
message.sentAn agent sends a message.
message.outgoingA message is sent to a contact (includes echoes).
contact.createdA new contact is created.
transfer.createdA transfer to a human agent is requested.
transfer.assignedA transfer is assigned to an agent.
transfer.resumedThe chatbot is resumed (transfer closed).

Every delivery is a POST with Content-Type: application/json and a User-Agent: Whatomate-Webhook/1.0 header. The body is:

{
"event": "message.incoming",
"timestamp": "2026-07-16T10:30:00Z",
"data": { /* event-specific object */ }
}

The data object depends on the event — message events carry message_id, contact_id, contact_phone, contact_name, message_type, content, and whatsapp_account; transfer events carry transfer_id, the contact fields, source, and (once assigned) agent_id / agent_name. Any custom headers you configured on the webhook are added to the request.

When a webhook has a secret, Whatomate signs each delivery and sends the signature in the X-Webhook-Signature header:

X-Webhook-Signature: sha256=<hex>

The signature is HMAC-SHA256 of the raw request body keyed with your secret, hex-encoded and prefixed with sha256=. Compute the same value on your side and compare in constant time:

import crypto from 'node:crypto';
function verify(rawBody, header, secret) {
const expected =
'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
// constant-time compare
return crypto.timingSafeEqual(Buffer.from(header), Buffer.from(expected));
}

Delivery is asynchronous — the API call that triggered the event returns immediately; webhook dispatch runs in the background on a detached context. For a given event, Whatomate:

  • Sends only to active webhooks that subscribe to that event.
  • Delivers to up to 10 webhooks concurrently per dispatch.
  • Treats any 2xx response as success. A non-2xx status or a network error is a failure.
  • Retries up to 3 times with exponential backoff between attempts (delays double: ~2s then ~4s). After the final attempt the failure is logged and the delivery is dropped — there is no persistent dead-letter queue.

Use POST /api/webhooks/{id}/test to send a one-off {"event":"test", ...} payload synchronously and confirm your endpoint accepts and verifies it.

Because webhook URLs are user-supplied, Whatomate guards against server-side request forgery on two levels:

  1. At save time (validateWebhookURL): the URL scheme must be http or https, and obvious internal targets are rejected — localhost, 0.0.0.0, *.local, *.internal, and IP literals in loopback, private, link-local, or unspecified ranges.
  2. At delivery time (SSRFSafeDialer): after DNS resolution, connections to loopback/private/link-local IPs are blocked — defending against DNS-rebinding attacks where a public hostname resolves to an internal address.

Full request/response schemas are in the API Reference → Webhooks.