Skip to content

Webhooks

Webhooks let Wabot push events to your server as they happen, so you don’t have to poll the API.

  1. In Wabot, open Developer → Webhook and click Add New.
  2. Enter your Webhook URI. It must be a public HTTPS endpoint that accepts POST requests with a JSON body.
  3. Tick the Web Events you want to receive.
  4. If you selected WhatsApp Message Received, choose which message sub-types to send (text, image, button reply and so on).
  5. Save. You can add several webhooks, for example one per system.

Sent when a customer sends you a message.

{ "messageId": "wamid.HBgMOTE5NDAwMjk0MTEwFQIAEhgg…", "status": "received" }

Use the message sub-types to choose which kinds of message trigger the event:

Text Message · Ad Message · Sticker Message · Image · Video · Document · Audio · Location Message · Contact Message · Simple Button Message (quick reply) · Radio Button Message (list reply) · Flow · Order · Unknown Message

To read the full message, call GET /v2/conversations/recent or the messages endpoint for that contact.

Sent when Meta approves, rejects or pauses one of your templates.

{ "template_id": "1234567890123456", "status": "APPROVED" }

Sent when a workflow run finishes successfully. You can see the data it collected under Settings → Data View.

{ "masterWorkflowId": 42, "status": "Success" }

Sent for campaign updates.

{ "msg": "Campaign completed", "responseData": { "campaign_id": 77, "name": "Diwali offer" } }

This event includes an X-Signature header. See Verify signatures.

  • Return any 2xx status quickly, within a few seconds. Do slow work in the background.
  • Wabot sends each event once and does not retry. If your endpoint is down, the event is lost, so for critical data, also reconcile periodically with the API.
  • Expect duplicates and out-of-order events, and make your handler idempotent, for example by keying on messageId.
app.post('/wabot/webhook', express.json(), (req, res) => {
res.sendStatus(200); // acknowledge first
queue.add('wabot-event', req.body); // process later
});

Campaign Completed requests include an X-Signature header. It is a hex HMAC-SHA256 of the JSON-encoded msg value, using your Client Secret as the key:

import crypto from 'node:crypto';
function isFromWabot(req) {
const expected = crypto
.createHmac('sha256', process.env.WABOT_CLIENT_SECRET)
.update(JSON.stringify(req.body.msg))
.digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(req.get('X-Signature') || ''));
}

The other events aren’t signed yet. Protect your endpoint with a hard-to-guess path, such as /wabot/webhook/7f3c…, and only accept requests over HTTPS.

To track a specific message instead of every event, pass callback_url and event when you send it. Wabot then posts sent, delivered, read, failed and replied updates for that message only.

Webhooks can also go the other way: your system calls Wabot to start a workflow. See Start a workflow from a webhook.