Webhooks: receive events in real time
Receive WhatsApp events and Instagram comments and DMs at your URL in real time, check the signature and understand the retries.
Updated on
With the Receive events (comments and DMs) by webhook scope (webhooks:manage), you set up a URL in your system and XBASE sends to it, in real time, the comments and DMs that reach the workspace's Instagram accounts.
Set up the webhook
PUT https://api.xbase.app/api/external/v1/webhookcurl -X PUT https://api.xbase.app/api/external/v1/webhook \
-H "Authorization: Bearer $XBASE_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "url": "https://mysystem.com/xbase/webhook", "events": ["instagram.comment", "instagram.message"] }'200 response:
{
"url": "https://mysystem.com/xbase/webhook",
"events": ["instagram.comment", "instagram.message"],
"secretLast4": "Q2w9",
"updatedAt": "2026-11-04T16:10:00.000Z",
"secret": "whsec_…"
}The secret appears only in this response: keep it to check the signature. Each key has one webhook. Calling the PUT again replaces the URL and the events and generates a new secret.
The URL must be https and public.
Get and turn off
GET /webhookreturns the URL, the events, the last 4 characters of the secret (secretLast4) andupdatedAt. With no webhook set up, it returns200with an empty body.DELETE /webhookturns off the webhook and returns204, with no body.- Revoking the key also turns off its webhook.
Events
| Event | When |
|---|---|
instagram.comment | Someone comments on a post from a workspace account |
instagram.message | A DM reaches a workspace account |
Each delivery is a POST to your URL:
POST https://mysystem.com/xbase/webhook
Content-Type: application/json
x-xbase-Signature: t=1793808600,v1=5d41402abc4b2a76b9719d911017c592…
x-xbase-Delivery: 0b9f7c1e-…
x-xbase-Event: instagram.comment{
"id": "0b9f7c1e-…",
"type": "instagram.comment",
"occurredAt": "2026-11-04T16:10:00Z",
"workspaceId": "8f0c2d4e-…",
"account": { "connectionId": "6d1f…", "instagramAccountId": "1784…", "username": "mycompany" },
"comment": {
"id": "17900000000000000",
"text": "How much is it?",
"mediaId": "18010000000000000",
"parentId": null,
"from": { "id": "…", "username": "johndoe" }
}
}In the instagram.message event, message comes instead of comment: { "mid", "text", "isEcho", "from": { "id" }, "attachments": [{ "type", "url" }] }.
The key's webhook sends Instagram events. WhatsApp events go out through the workspace webhooks.
Workspace webhooks: WhatsApp and Instagram
To receive WhatsApp events, set up the webhook in Integrations → Webhooks (or with the MCP tools create-webhook and update-webhook). It needs the permission to manage integrations. A workspace can have up to 10 webhooks, and each one picks:
- Numbers: all the workspace's WhatsApp numbers or only the ones you choose.
- Events, from the table below.
- Format: XBASE (standard), signed.
| Event | When |
|---|---|
whatsapp.message.received | A message reaches a workspace number, including the tapped button, the row chosen in a list and a Flow answer |
whatsapp.message.sent · .delivered · .read · .failed | A sent message changes status |
instagram.comment · instagram.message | The same as on this page |
The delivery uses the same headers, signature and retries as on this page. The body carries the number and, in payload, the event in Meta's webhook format, with a single item (messages[0] or statuses[0]):
{
"id": "3c2a…",
"type": "whatsapp.message.delivered",
"occurredAt": "2026-11-04T16:10:00Z",
"workspaceId": "8f0c2d4e-…",
"connection": { "id": "6d1f…", "provider": "CLOUD", "appName": null, "number": "+55 11 99999-0000" },
"payload": {
"object": "whatsapp_business_account",
"entry": [{
"id": "102290129340398",
"changes": [{
"field": "messages",
"value": {
"messaging_product": "whatsapp",
"metadata": { "display_phone_number": "5511999990000", "phone_number_id": "106540352242922" },
"statuses": [{ "id": "wamid.…", "status": "delivered", "timestamp": "1793808600", "recipient_id": "5521999990000" }]
}
}]
}]
}
}connection.id is the number id in GET /whatsapp/numbers. The item id (messages[0].id or statuses[0].id) is the messageId of the WhatsApp API: the one in the send responses and the one you use to react and to mark as read.
The signing secret shows up once, on the screen, right after you create the webhook. The same delivery can arrive more than once: ignore repeats by the item id.
Check the signature
The x-xbase-Signature header has the time (t, in seconds) and the signature (v1): the HMAC-SHA256, in hexadecimal, of "<t>.<body>" with your secret. Reject the delivery if the signature does not match or if t is more than 5 minutes old. Example in Node.js:
import { createHmac, timingSafeEqual } from 'node:crypto';
// rawBody: the body exactly as it arrived, before any JSON.parse.
function isValidSignature(rawBody, header, secret) {
const { t, v1 } = Object.fromEntries(header.split(',').map((part) => part.split('=')));
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false; // up to 5 minutes
const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
return v1.length === expected.length && timingSafeEqual(Buffer.from(v1), Buffer.from(expected));
}Delivery and retries
- A delivery counts as done when your URL returns 2xx within 10 seconds. Reply fast and process later.
- Without a 2xx, XBASE tries 5 more times: after 1 minute, 5 minutes, 30 minutes, 2 hours and 6 hours. After that, it gives up.
- The same delivery can arrive more than once. Ignore repeats by
x-xbase-Delivery, or bycomment.idandmessage.mid.