API docsLog inCreate account
Webhooks

Webhooks

POST/v1/subscriptions

Instead 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

FieldTypeNotes
urlstringRequired. Your https endpoint
eventsstring[]Required. Event types to receive (below)
labelstringOptional name
filtersobjectOptional {"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

EventWhen
message.receivedA message arrived. Polls and contact cards you send also fire it, so check is_from_me
message.sentA message was sent from this number
message.deliveredA sent message reached the phone
message.readA sent message was read
message.failedA sent message failed
tenant.pairedThe phone was linked
tenant.connection_changedThe 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

EndpointDoes
GET /v1/subscriptionsLists your subscriptions
GET /v1/subscriptions/:idOne subscription
PATCH /v1/subscriptions/:idChanges label, url, events, filters or paused
POST /v1/subscriptions/:id/rotate-secretNew secret (shown once); the old one stops working
POST /v1/subscriptions/:id/pingSends a test message.received event
GET /v1/subscriptions/:id/deliveries?limit=Recent attempts: status, response code, latency, last error
DELETE /v1/subscriptions/:idRemoves it, with any pending deliveries

Missed events while your service was down? See Event log.