Webhooks
Webhooks
POST
/v1/subscriptionsInstead of polling, register a URL and we POST signed events to it as they happen.
Create a subscription
POST https://api.api-supp.com/v1/subscriptions
| Field | Type | Notes |
|---|---|---|
url | string | Required. Your https endpoint |
events | string[] | Required. Event types to receive (below) |
label | string | Optional name |
filters | object | Optional {"chat_jid": "…"} to receive one chat only |
json
{ "url": "https://example.com/whatsapp-hook", "events": ["message.received", "message.read"], "label": "CRM" }The response includes id and a signing secret. The secret is shown only once; store it.
Event types
| Event | When |
|---|---|
message.received | A message arrived. Polls and contact cards you send also fire it, so check is_from_me |
message.sent | A message was sent from this number |
message.delivered | A sent message reached the phone |
message.read | A sent message was read |
message.failed | A sent message failed |
tenant.paired | The phone was linked |
tenant.connection_changed | The number connected or disconnected |
What you receive
json
{
"id": "evt_01HXAB...",
"type": "message.received",
"occurred_at": "2026-05-07T16:01:32Z",
"api_version": "1",
"chat_jid": "14155550132@s.whatsapp.net",
"data": { }
}data by type:
message.received,message.sent:{id, chat_jid, sender, content, timestamp, is_from_me, media_type, filename, url}message.delivered,message.read,message.failed:{id, chat_jid, delivery_status}tenant.paired,tenant.connection_changed:{state}
Verify the signature
Every call carries:
X-Bridge-Signature: t=<unix_secs>,v1=<hex_hmac_sha256>Compute HMAC-SHA256 of t + "." + rawBody with your secret, compare in constant time, and reject events whose t is more than 5 minutes off. Node example:
js
import crypto from 'node:crypto';
function verify(rawBody, header, secret) {
const { t, v1 } = Object.fromEntries(header.split(',').map(kv => kv.split('=')));
if (Math.abs(Date.now()/1000 - Number(t)) > 300) return false;
const expected = crypto.createHmac('sha256', secret)
.update(`${t}.${rawBody}`).digest('hex');
return crypto.timingSafeEqual(Buffer.from(v1, 'hex'), Buffer.from(expected, 'hex'));
}Retries
Return any 2xx to acknowledge. On 5xx or a network failure we retry with growing delays (12 attempts over about 3 days). On a 4xx other than 408 or 429 the delivery stops immediately, since that points at a bug in the receiver.
Manage subscriptions
| Endpoint | Does |
|---|---|
GET /v1/subscriptions | Lists your subscriptions |
GET /v1/subscriptions/:id | One subscription |
PATCH /v1/subscriptions/:id | Changes label, url, events, filters or paused |
POST /v1/subscriptions/:id/rotate-secret | New secret (shown once); the old one stops working |
POST /v1/subscriptions/:id/ping | Sends a test message.received event |
GET /v1/subscriptions/:id/deliveries?limit= | Recent attempts: status, response code, latency, last error |
DELETE /v1/subscriptions/:id | Removes it, with any pending deliveries |
Missed events while your service was down? See Event log.