Webhooks
WhatsApp events
Signed webhooks for the WhatsApp onboarding lifecycle.
During WhatsApp onboarding, OnCore POSTs lifecycle events to your configured server URL as they happen. Unlike the call and conversation webhooks, these deliveries are HMAC-signed and retried.
Events#
| Event | Fires when |
|---|---|
whatsapp.onboarding.tier_pending | Number verified and registered but below Meta's calling messaging-limit tier; parked until the portfolio limit is raised |
whatsapp.onboarding.completed | Calling enabled on the number |
whatsapp.onboarding.failed | Terminal failure — branch on data.reason |
whatsapp.calling.live | Display name approved and calling is live end-to-end |
Payload#
POST body
{
"event": "whatsapp.onboarding.completed",
"sessionId": "0194cc1a-eea6-7351-903d-9dcf479e14ae",
"tenantId": "0194cc1a-eea6-7351-903d-9dcf479e14ae",
"phoneNumber": "+15555550123",
"timestamp": "2026-07-15T18:00:00.000Z",
"data": { "messagingLimitTier": "TIER_2K" }
}| Field | Type | Description |
|---|---|---|
event | string | One of the four event types above |
sessionId | string | The onboarding session this event concerns |
tenantId | string | The tenant (end-business) the number belongs to |
phoneNumber | string | The E.164 number being onboarded |
timestamp | string | ISO 8601 time the event was generated (server time) |
data | object | Event-specific payload — treat as an open object, new keys may be added |
Known data keys:
data.reason— set onfailed; the failure code (see the full taxonomy, e.g.attach_failed:133016,token_revoked,otp_call_timeout)data.retryAfterHours— onotp_request_limitfailures: hours to wait before retryingdata.messagingLimitTier— ontier_pending/completed: Meta's messaging-limit tier (e.g.TIER_2K, orunknown)data.enableCallingDeferred— ontier_pending:truewhen the tier was eligible but the enable-calling call is deferred for retry
Verifying deliveries#
Every POST carries two headers:
X-OnCore-Signature: sha256=<hex>— HMAC-SHA256 of the raw request body bytes (verify before JSON-parsing), keyed by your per-tenant client server secret (the same secret used for end-of-call reports, shown on the API Keys page in your OnCore dashboard)X-OnCore-Delivery: <uuid>— the delivery id
Recompute "sha256=" + hex(hmacSHA256(secret, rawBody)) and compare with a constant-time equality check; reject on mismatch:
Verification (Node.js)
import { createHmac, timingSafeEqual } from "crypto";
export function verifyOnCoreSignature(rawBody: Buffer, signatureHeader: string, secret: string): boolean {
const expected = `sha256=${createHmac("sha256", secret).update(rawBody).digest("hex")}`;
const a = Buffer.from(expected);
const b = Buffer.from(signatureHeader);
return a.length === b.length && timingSafeEqual(a, b);
}Delivery and retries#
Delivery is at-least-once:
- A non-2xx response or timeout is retried up to 5 attempts with exponential backoff (1s, 2s, 4s, 8s between attempts).
- Each attempt has a 10-second timeout.
- All retries of one event reuse the same
X-OnCore-Deliveryid — treat it as the idempotency key and dedupe on it so a retried delivery isn't processed twice. - Respond
2xxpromptly to stop retries. - Events may arrive out of order: treat
timestampas the ordering hint, and the sessionstatusfromGET /whatsapp/onboarding-sessions/{id}as authoritative.
If your endpoint is down for long
After the 5th failed attempt the event is dropped (there is no dead-letter redelivery). You can always recover the current state by polling the session status endpoint.