Search for a command to run...
Core concept
HMAC-SHA256Receive inbound messages and delivery lifecycle events through one signed endpoint, with channel and client_ref included for deterministic routing.
Save the receiver URL and event selection in the dashboard's Webhooks section. Copy the generated signing secret into your application's secret manager. Webhook configuration is currently managed through the portal, not a public /v1/webhooks route.
Subscribe only to events your product handles. New fields can be added over time, so ignore unknown properties.
| event | Channel | Meaning |
|---|---|---|
| message.received | WhatsApp, Instagram | An end user sent a message. |
| message.status | A sent message moved to sent, delivered, read, or failed. | |
| message.reaction | WhatsApp, Instagram | An end user added or removed a reaction. |
| message.echo | The connected professional account sent a message. | |
| message.read | A user read a message. | |
| message.postback | A user activated a postback action. | |
| webhook.test | All | A signed diagnostic event from the dashboard. |
Use event to select a handler, channel for the provider-specific body, and client_ref to find your customer. Provider data stays close to the source payload so you can preserve message fidelity.
{
"event": "message.received",
"channel": "whatsapp",
"timestamp": "2026-07-24T09:42:18.000Z",
"client_ref": "customer_42",
"phone_number": "+254711111111",
"contacts": [
{
"profile": { "name": "Amina" },
"wa_id": "254700000000"
}
],
"messages": [
{
"from": "254700000000",
"id": "wamid.xxx",
"type": "text",
"text": { "body": "Is my order ready?" }
}
]
}{
"event": "message.received",
"channel": "instagram",
"timestamp": "2026-07-24T09:44:01.000Z",
"client_ref": "customer_42_instagram",
"contact": {
"id": "17841400000000000",
"username": "ama_mensah",
"name": "Ama Mensah"
},
"messages": [
{
"id": "mid.xxx",
"sender": {
"id": "17841400000000000",
"username": "ama_mensah",
"name": "Ama Mensah"
},
"recipient": { "id": "17841499999999999" },
"text": "Can you help?"
}
]
}Downloading media a customer sent
messages[].image.id and friends. Fetching the file needs the client's Meta credentials, which you never hold, so request it from us: GET /v1/media/<media_id>?client_ref=<ref> with your API key (clients:read scope). It streams the bytes back with the real Content-Type, plus Content-Length and an X-Media-Sha256 you can verify against the webhook. Meta keeps inbound media for 30 days.attachments[].payload.url. It is short-lived, so download on receipt rather than when someone opens the conversation.Instagram users arrive with their handle
username and name, and the person the event is about is repeated at the top level as contact. Both are omitted when Instagram has no profile to return, so treat them as optional and keep keying your records on the ID.Store provider message IDs
Read X-Intelli-Signature, compute HMAC-SHA256 over the exact raw request bytes, prefix the hex digest with sha256=, and compare in constant time.
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyIntelliSignature(
rawBody: Buffer,
signature: string,
secret: string,
) {
const expected =
"sha256=" +
createHmac("sha256", secret).update(rawBody).digest("hex");
const received = Buffer.from(signature);
const calculated = Buffer.from(expected);
return (
received.length === calculated.length &&
timingSafeEqual(received, calculated)
);
}Do not re-serialize JSON
Verify, persist or enqueue, and return a 2xx response within five seconds. Move database fan-out, AI work, outbound replies, and third party calls into an asynchronous worker.
Immediate
10 seconds
60 seconds
5 minutes
Non-2xx responses and timeouts are retried on this schedule. After the final attempt, the delivery remains available in logs for inspection and manual redelivery.