Skip to content

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?

  1. Expose an HTTPS endpoint

    It must accept POST with a JSON body and answer with any 2xx status within 5 seconds. Plain http://, localhost, 127.0.0.1, ::1 and *.local URLs are rejected.

  2. 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.

  3. 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?

ConnectionX-Whatsappx-EventBodyWhen
Linked by QR codescan.whatsappwhatsappx.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.whatsappMeta’s original webhook payload, unchangedEach 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?

HeaderValue
Content-Typeapplication/json
User-AgentWhatsappx-Webhook-Forwarder/1.0
X-Whatsappx-Eventscan.whatsapp or meta.whatsapp
X-Hub-Signature-256sha256= + hex HMAC-SHA256 of the raw body, keyed with your webhook secret
X-Whatsappx-Webhook-TokenYour 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 2xx status 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.

Webhook endpoints (Console only)