Webhooks and events
Webhooks: receive incoming WhatsApp messages
Outbound webhooks POST every incoming WhatsApp message of a workspace to your HTTPS URL as JSON, signed with HMAC-SHA256 in the X-Hub-Signature-256 header. Owners and admins set them up in the app.
How do I set up a webhook?
Expose an HTTPS endpoint
It must accept
POSTwith a JSON body and answer with any2xxstatus within 5 seconds. Plainhttp://,localhost,127.0.0.1,::1and*.localURLs are rejected.Add it in the app
As an owner or admin, open Webhooks, paste the URL and save. Optionally set or generate a shared token. A workspace can have up to 10 webhooks.
Store the signing secret
The
secret(whsec_+ 48 hex characters) is shown only once, right after creation. Use it to verify every delivery.
What is delivered?
| Connection | X-Whatsappx-Event | Body | When |
|---|---|---|---|
| Linked by QR code | scan.whatsapp | whatsappx.si JSON with event: "message.inbound" | Each new incoming message received live (not your own messages and not history synced when linking). |
| Cloud API (Meta) | meta.whatsapp | Meta’s original webhook payload, unchanged | Each Meta webhook call for the workspace’s number: incoming messages, plus message template status and quality updates. Forwarded after Meta’s signature is checked. |
Every enabled webhook of the workspace receives every delivery. The events field on a webhook is always messages and is not a filter. Payload details: Webhook events.
Which headers does each delivery have?
| Header | Value |
|---|---|
Content-Type | application/json |
User-Agent | Whatsappx-Webhook-Forwarder/1.0 |
X-Whatsappx-Event | scan.whatsapp or meta.whatsapp |
X-Hub-Signature-256 | sha256= + hex HMAC-SHA256 of the raw body, keyed with your webhook secret |
X-Whatsappx-Webhook-Token | Your shared token, only when one is set |
How do I verify the signature?
Compute HMAC-SHA256 over the raw request body (the exact bytes, before JSON parsing) with the webhook secret as the key, hex-encode it, prefix sha256= and compare it with X-Hub-Signature-256 in constant time. Reject the request if they differ.
import crypto from 'node:crypto';
import express from 'express';
const app = express();
const SECRET = process.env.WHATSAPPX_WEBHOOK_SECRET; // whsec_...
app.post('/whatsappx/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const expected = 'sha256=' + crypto.createHmac('sha256', SECRET).update(req.body).digest('hex');
const received = req.get('X-Hub-Signature-256') || '';
const ok =
received.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected));
if (!ok) return res.status(401).end();
res.status(200).end(); // answer fast, then process
const event = req.get('X-Whatsappx-Event');
const payload = JSON.parse(req.body.toString('utf8'));
if (event === 'scan.whatsapp') {
console.log(payload.chat_phone, payload.content);
}
});
app.listen(3000);What happens when my endpoint fails?
- Each delivery has a 5-second timeout. Any
2xxstatus counts as success; anything else, or a timeout, is a failure. - A failed delivery is retried up to 3 attempts in total, waiting 300 ms after the first failure and 600 ms after the second.
- One event is delivered to the workspace’s webhooks one after another, and all attempts for all of them share a 30-second budget. A slow endpoint can therefore delay or cut off deliveries to the others.
- After the last attempt the delivery is dropped. Deliveries are not stored: there is no delivery log and no replay.
Because events can be missed while your endpoint is down, reconcile periodically with List conversations and List messages.
How do I handle duplicates?
A retry can deliver the same message twice (for example when your endpoint processed it but answered too slowly). Deduplicate on whatsapp_message_id for scan.whatsapp, and on the message id inside Meta’s payload for meta.whatsapp.
What is the shared token for?
If you set a token (8–256 characters) or let whatsappx.si generate one (whtok_ + 48 hex characters), it is sent in X-Whatsappx-Webhook-Token with every delivery. It is a simple extra check, for example for a gateway that can compare a header but cannot compute HMACs. Always verify the signature as well.