Webhooks are configured per WhatsApp account via the admin dashboard or REST:
curl -X POST "$PUBLIC_API_URL/v1/accounts/ACCOUNT_ID/webhooks" \
-H "Authorization: Bearer $ADMIN_JWT" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/wa-events",
"events": ["*"],
"retry_count": 5,
"timeout_seconds": 15
}'
Response includes the secret — store it now, it is shown only once.
Every delivery is a POST with this body shape:
{
"event": "message.received",
"account_id": "acc_...",
"timestamp": "2026-05-25T00:00:00.000Z",
"data": {
"message_id": "...",
"wa_message_id": "...",
"chat_id": "971...@s.whatsapp.net",
"chat_type": "private",
"direction": "incoming",
"type": "text",
"from": "971501234567",
"to": null,
"body": "Hello"
}
}
| Header | Value |
|---|---|
| Content-Type | application/json |
| User-Agent | WhatsAppGateway-Webhook/1.0 |
| X-Webhook-Event | e.g. message.received |
| X-Webhook-Delivery-Id | unique per attempt |
| X-Webhook-Timestamp | ISO timestamp |
| X-Webhook-Signature | sha256= HMAC of raw body using webhook secret |
X-Webhook-Signature uses HMAC-SHA256 over the raw request body using the webhook secret.
Node:
import crypto from 'node:crypto';
function verify(rawBody, header, secret) {
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
const got = (header || '').replace(/^sha256=/, '');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(got));
}
Python:
import hmac, hashlib
def verify(raw_body, header, secret):
expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
got = (header or "").replace("sha256=", "")
return hmac.compare_digest(expected, got)
message.received, message.sent, message.delivered, message.read, message.deleted,chat.created, chat.updated, group.participant_added, group.participant_removed,connection.qr, connection.connected, connection.disconnected, connection.error,ai.reply.generated, ai.reply.sent, ai.reply.failed.
Use "*" in the events array to subscribe to all.
Failed deliveries (non-2xx, timeout, network error) are retried by a BullMQ worker with exponential backoff (5s, 25s, 2m, 10m, 50m, capped at the webhook's retry_count). Inspect history in the dashboard or via GET /api/v1/webhook-deliveries.
| Symptom | Fix |
|---|---|
| You see deliveries marked failed with code 0 | Your endpoint is unreachable from the server — check firewall / DNS |
| code 401/403 | Your endpoint requires its own auth — most webhooks should be public |
| Signature mismatch | You verified against a stringified copy — always sign over the raw body |