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. | |
| template.status | Meta finished reviewing a message template. Carries template.status (APPROVED, REJECTED, PAUSED, …) and template.reason. | |
| user.preferences | A user opted in or out of marketing messages. | |
| 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.
Copy the integration contract into your assistant, then let it tailor the implementation to your stack.
You are helping me receive Intelli Partner API webhooks in my product — events from the WhatsApp and Instagram accounts connected through Intelli (my clients' accounts, or my own).
Before writing code, ask me:
1. My backend framework/language.
2. Whether I already have a public HTTPS endpoint for webhooks.
Setup:
- I configure the receiver URL and event selection in the Intelli portal (Webhooks page). INTELLI_WEBHOOK_SECRET (shown there) signs every delivery; keep it server-side.
A) Verify EVERY delivery before parsing:
- Read the X-Intelli-Signature header, compute HMAC-SHA256 over the RAW request body with INTELLI_WEBHOOK_SECRET, and constant-time compare against "sha256=<hex digest>". Reject mismatches with 401. Never parse the JSON before the signature checks out, and never verify against a re-serialized body — byte-for-byte raw only.
B) Respond fast, process async, stay idempotent:
- Return 2xx immediately and hand the payload to a queue/worker. Failed or slow deliveries are retried on my configured retry policy, so deliveries can arrive more than once and out of order — dedupe on the message id inside the payload.
C) Events — every payload carries event, channel and client_ref so I can route to the right account:
- message.received (WhatsApp, Instagram): an end user wrote in. WhatsApp media arrives as an id — fetch it with GET /media/{media_id}?client_ref=... while it's valid (30 days). Instagram media carries a short-lived URL at attachments[].payload.url — download on receipt.
- message.status (WhatsApp): a message I sent moved to sent, delivered, read or failed — this is where ASYNCHRONOUS delivery failures surface (e.g. a header media link Meta couldn't fetch). Store the status against my message_id and alert on failed.
- message.reaction (WhatsApp, Instagram): a reaction was added or removed.
- message.echo (Instagram): the connected professional account itself sent something.
- message.read (Instagram): the user read my message.
- message.postback (Instagram): the user activated a postback action.
- template.status (WhatsApp): Meta finished reviewing a template — carries template.status (APPROVED, REJECTED, PAUSED, …) and template.reason. Flip my UI to sendable on APPROVED; show the reason on REJECTED.
- user.preferences (WhatsApp): the user opted in or out of marketing — honor it before sending marketing templates.
- webhook.test: a signed diagnostic event I can fire from the dashboard.
D) Tooling while I build:
- The portal's webhook simulator fires real signed sample events at my endpoint, and Delivery Logs show every attempt (with a resend button) so I can debug without waiting for real traffic.
Write idiomatic, production-quality code for my stack: a verified receiver, an async processor with idempotency, and handlers for the events I use.