API reference
Webhooks
Signed event delivery for messages, contacts and campaigns.
Webhooks push events to your server as they happen, so you don’t need to poll. Add an endpoint under Webhooks in the dashboard, choose the events you want, and copy the signing secret — it’s shown once.
Events
| Event | Sent when |
|---|---|
message.received | A customer sends you a message |
message.sent | WhatsApp accepted an outbound message |
message.delivered | Delivered to the customer’s device |
message.read | The customer read it |
message.failed | Delivery failed |
contact.created | A new contact was created |
campaign.completed | A campaign finished sending |
Registered events: message.received, message.sent, message.delivered, message.read, message.failed, contact.created, campaign.completed. You can also send a ping test from the dashboard.
Request format
We send an HTTPS POST with a JSON body and these headers:
| Header | Value |
|---|---|
X-Waba-Event | Event name, e.g. message.received |
X-Waba-Timestamp | Unix time (seconds) when the request was signed |
X-Waba-Signature | sha256= + hex HMAC (see below) |
User-Agent | WABA-Panel-Webhook/1.0 |
{
"event": "message.received",
"created_at": "2026-10-07T10:15:00+00:00",
"data": {
"id": 101, "wamid": "wamid.HBgM…", "from": "14155550100",
"type": "text", "body": "Hi!", "name": "Jane"
}
}Verify the signature
The signature is HMAC-SHA256(secret, timestamp + "." + rawBody). Use the raw request body — not re-serialised JSON — and a constant-time comparison. Reject requests whose timestamp is more than five minutes old to prevent replays.
const crypto = require('crypto');
app.post('/waba-webhook', express.raw({ type: 'application/json' }), (req, res) => {
const ts = req.headers['x-waba-timestamp'];
const sig = req.headers['x-waba-signature'];
const expected = 'sha256=' + crypto.createHmac('sha256', process.env.WABA_SECRET)
.update(ts + '.' + req.body.toString('utf8')).digest('hex');
const ok = sig && sig.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
if (!ok) return res.sendStatus(401);
if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return res.sendStatus(400);
const event = JSON.parse(req.body);
// …handle event, then reply fast…
res.sendStatus(200);
});<?php
$ts = $_SERVER['HTTP_X_WABA_TIMESTAMP'];
$sig = $_SERVER['HTTP_X_WABA_SIGNATURE'];
$body = file_get_contents('php://input');
$expected = 'sha256=' . hash_hmac('sha256', $ts . '.' . $body, getenv('WABA_SECRET'));
if (!hash_equals($expected, $sig) || abs(time() - (int)$ts) > 300) { http_response_code(401); exit; }Delivery & retries
- Respond with any 2xx within 10 seconds. Do slow work in a background job.
- On a non-2xx response, timeout or network error we retry up to 4 attempts in total, waiting 30 s, 2 min, then 10 min.
- Redirects are not followed. Endpoints must be public
httpsURLs — private and internal addresses are blocked. - After repeated consecutive failures the endpoint is disabled automatically. Fix the problem and re-enable it in the dashboard.
- Delivery can repeat, and ordering is not guaranteed — make handlers idempotent (e.g. de-duplicate on
data.id/wamid). - Every attempt, with response code and body excerpt, is visible in the endpoint’s delivery log in the dashboard.
Related features
REST API & webhooksA clean REST API for WhatsApp Cloud API messaging with scoped keys, IP allow-lists, rate limits and HMAC-signed webhooks. Docs included.Chatbot & automationAnswer customers 24/7 with keyword and regex auto-replies, welcome messages and visual message flows. Optional AI assistant add-on.