API and webhooks

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/webhook
curl -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 /webhook returns the URL, the events, the last 4 characters of the secret (secretLast4) and updatedAt. With no webhook set up, it returns 200 with an empty body.
  • DELETE /webhook turns off the webhook and returns 204, with no body.
  • Revoking the key also turns off its webhook.

Events

EventWhen
instagram.commentSomeone comments on a post from a workspace account
instagram.messageA 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.
EventWhen
whatsapp.message.receivedA message reaches a workspace number, including the tapped button, the row chosen in a list and a Flow answer
whatsapp.message.sent · .delivered · .read · .failedA sent message changes status
instagram.comment · instagram.messageThe 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 by comment.id and message.mid.

Frequently asked questions

See also

On this page