Webhooks
Receive real-time WhatsApp events on your server
Configure a webhook URL in Settings → Webhook to receive real-time events from your WhatsApp clients.
Envelope
Every event is delivered as a POST request with a JSON body in this shape:
{
"type": "whatsapp" | "signup_completed" | "template_status" | "flow_completion" | "test",
"client_id": "<uuid>",
"external_id": "<your-reference-id>",
"payload": { ... }
}| Field | Type | Description |
|---|---|---|
type | string | Event type — see below |
client_id | string | UUID of the client that triggered the event |
external_id | string | null | Your own reference ID set on the client (useful for correlation) |
payload | object | Event-specific data — shape depends on type |
Event Types
type: "whatsapp"
Fired for every inbound update from the Meta WhatsApp Cloud API — messages, status changes, etc. The payload is the raw Meta webhook body, forwarded unchanged.
Inbound message example:
{
"type": "whatsapp",
"client_id": "f3a1c2d4-...",
"external_id": "acme_eu",
"payload": {
"object": "whatsapp_business_account",
"entry": [
{
"id": "<waba_id>",
"changes": [
{
"field": "messages",
"value": {
"messaging_product": "whatsapp",
"metadata": {
"phone_number_id": "987654321",
"display_phone_number": "+1 555..."
},
"messages": [
{
"from": "1234567890",
"id": "wamid.xxx",
"timestamp": "1713600000",
"type": "text",
"text": { "body": "Hello!" }
}
]
}
}
]
}
]
}
}Message status update example:
{
"type": "whatsapp",
"client_id": "f3a1c2d4-...",
"external_id": "acme_eu",
"payload": {
"object": "whatsapp_business_account",
"entry": [
{
"id": "<waba_id>",
"changes": [
{
"field": "messages",
"value": {
"messaging_product": "whatsapp",
"metadata": { "phone_number_id": "987654321" },
"statuses": [
{
"id": "wamid.xxx",
"status": "delivered",
"timestamp": "1713600010",
"recipient_id": "1234567890"
}
]
}
}
]
}
]
}
}Possible status values: sent, delivered, read, failed
type: "signup_completed"
Fired once after an embed signup OAuth flow finishes — the Meta authorization code has been exchanged for an access token, the phone number registered, and the webhook listener activated.
{
"type": "signup_completed",
"client_id": "f3a1c2d4-...",
"external_id": "acme_eu",
"payload": {
"waba_id": "123456789",
"phone_number_id": "987654321",
"catalog_id": "456789123",
"meta_business_id": "111222333",
"signup_status": "completed"
}
}| Field | Type | Description |
|---|---|---|
waba_id | string | WhatsApp Business Account ID |
phone_number_id | string | Meta phone number ID |
catalog_id | string | null | Meta Commerce catalog ID (if linked during signup) |
meta_business_id | string | null | Meta Business ID associated with the WABA |
signup_status | "completed" | Always "completed" for this event |
type: "template_status"
Fired when Meta changes a message template's review status (approved, rejected, paused, disabled…).
{
"type": "template_status",
"client_id": "f3a1c2d4-...",
"external_id": "acme_eu",
"payload": {
"template_id": "112233445566",
"template_name": "order_update",
"language": "en_US",
"status": "APPROVED",
"reason": null
}
}status values include APPROVED, REJECTED, PAUSED, DISABLED, PENDING, IN_APPEAL. reason is set when a template is rejected.
type: "flow_completion"
Fired when a user submits (completes) a WhatsApp Flow you sent.
{
"type": "flow_completion",
"client_id": "f3a1c2d4-...",
"external_id": "acme_eu",
"payload": {
"message_id": "wamid.xxx",
"from": "1234567890",
"token": "<your-flow-token>",
"response": { "screen_0_name": "Jane", "screen_0_choice": "demo" }
}
}token is the flow_token you passed when sending the flow; response contains the fields collected by the flow.
type: "test"
Sent when you click Send test event in the dashboard (or call POST /api/clients/{id}/webhook/test). Use it to verify connectivity and signature validation.
Delivery & Retries
- Your endpoint must respond with a
2xxstatus code within 10 seconds. - Non-2xx responses and network errors are retried up to 3 attempts with increasing backoff (immediately, +5s, +25s).
- Every delivery attempt series is recorded. Inspect them per client in the dashboard (Clients → Webhooks) or via
GET /api/clients/{id}/webhook/deliveries— status, attempt count, HTTP response code, and last error. - Failed deliveries never affect the API response to the original caller.
Security
If a webhook secret is configured (Settings → Webhook), every request includes an HMAC signature header:
X-Signature-256: sha256=<hex digest>The digest is HMAC-SHA256(secret, raw_request_body). Recompute it on your server and compare with a constant-time comparison before trusting the payload. Also validate the client_id field against your known client IDs.