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.
Subscribing to events
Section titled “Subscribing to events”Manage webhooks under Settings → Webhooks, or via the API:
GET /api/webhooks— list webhooks (also returns theavailable_eventscatalog).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.
Event catalog
Section titled “Event catalog”Seven event types can be subscribed to:
| Event | Fires when |
|---|---|
message.incoming | A new message is received from a contact. |
message.sent | An agent sends a message. |
message.outgoing | A message is sent to a contact (includes echoes). |
contact.created | A new contact is created. |
transfer.created | A transfer to a human agent is requested. |
transfer.assigned | A transfer is assigned to an agent. |
transfer.resumed | The chatbot is resumed (transfer closed). |
Payload
Section titled “Payload”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.
Verifying the signature
Section titled “Verifying the signature”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, retries & concurrency
Section titled “Delivery, retries & concurrency”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.
SSRF protection
Section titled “SSRF protection”Because webhook URLs are user-supplied, Whatomate guards against server-side request forgery on two levels:
- At save time (
validateWebhookURL): the URL scheme must behttporhttps, and obvious internal targets are rejected —localhost,0.0.0.0,*.local,*.internal, and IP literals in loopback, private, link-local, or unspecified ranges. - 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.