Skip to content

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#

EventFires when
whatsapp.onboarding.tier_pendingNumber verified and registered but below Meta's calling messaging-limit tier; parked until the portfolio limit is raised
whatsapp.onboarding.completedCalling enabled on the number
whatsapp.onboarding.failedTerminal failure — branch on data.reason
whatsapp.calling.liveDisplay 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" }
}
FieldTypeDescription
eventstringOne of the four event types above
sessionIdstringThe onboarding session this event concerns
tenantIdstringThe tenant (end-business) the number belongs to
phoneNumberstringThe E.164 number being onboarded
timestampstringISO 8601 time the event was generated (server time)
dataobjectEvent-specific payload — treat as an open object, new keys may be added

Known data keys:

  • data.reason — set on failed; the failure code (see the full taxonomy, e.g. attach_failed:133016, token_revoked, otp_call_timeout)
  • data.retryAfterHours — on otp_request_limit failures: hours to wait before retrying
  • data.messagingLimitTier — on tier_pending / completed: Meta's messaging-limit tier (e.g. TIER_2K, or unknown)
  • data.enableCallingDeferred — on tier_pending: true when 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-Delivery id — treat it as the idempotency key and dedupe on it so a retried delivery isn't processed twice.
  • Respond 2xx promptly to stop retries.
  • Events may arrive out of order: treat timestamp as the ordering hint, and the session status from GET /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.