Skip to Content
WebhooksWhatsApp Events

WhatsApp Events

Every Cloud API webhook for a customer that’s been onboarded through TP-Signup is forwarded to your account-level Webhook URL (set under Settings → Webhook URL on the partner panel). Three categories:

  • Onboardingwhatsapp.waba_onboarded fires once when the customer completes embedded signup. Internal tokens stripped.
  • Status updatessent / delivered / read / failed / deleted for every outbound message you send.
  • Inbound messages — text, media, location, interactive responses, and the other Cloud API message types.

WABA → customer mapping is done server-side at TP-Signup time. You only ever see events for customers under your account; cross-tenant leakage is impossible.

Headers

Every POST carries:

HeaderValue
Content-Typeapplication/json
X-Webhook-Sourcesplashifypro
X-Webhook-Typemeta_raw for status & inbound; absent for onboarding events

1. whatsapp.waba_onboarded

Fires once when the upstream onboarding completes for a customer. The isv_name_token field that Meta includes in this event is stripped before delivery — do not expect or rely on it.

{ "event": "WABA_ONBOARDED", "source": "splashifypro", "waba_id": "123456789012345", "phone_number_id": "623925589026353", "phone_number": "+919999999999" }

Once you receive this, the customer’s phone numbers are immediately available via the Phone Numbers endpoint.

2. Status updates (sent / delivered / read / failed / deleted)

These follow the standard WhatsApp Cloud API envelope. The full raw Meta payload is forwarded as-is. Status events all share the same top-level shape; the statuses[].status field tells you which it is.

sent

Fires when the upstream accepts your outbound for delivery. The conversation.origin.type field reflects the session origin (marketing / utility / authentication / service / referral_conversion).

{ "object": "whatsapp_business_account", "entry": [{ "id": "3130247400631305", "changes": [{ "value": { "messaging_product": "whatsapp", "metadata": { "display_phone_number": "91XXXXXXXXXX", "phone_number_id": "623925589026353" }, "statuses": [{ "id": "wamid.HBgM...", "status": "sent", "timestamp": "1655287862", "recipient_id": "91XXXXXXXXXX", "conversation": { "id": "92d5c04d20c643078be036db3ac05026", "expiration_timestamp": "1655372820", "origin": { "type": "marketing" } }, "pricing": { "billable": true, "pricing_model": "CBP", "category": "marketing" } }] }, "field": "messages" }] }] }

delivered / read

Same envelope, with statuses[].status set to delivered or read. pricing may be absent on basic read callbacks. Use these to update your message-tracker UI; the billing trigger on our side is controlled by the per-partner deduction-trigger toggle (admin-side).

failed

{ "object": "whatsapp_business_account", "entry": [{ "id": "1568505090181585", "changes": [{ "value": { "messaging_product": "whatsapp", "metadata": { "...": "..." }, "statuses": [{ "id": "wamid.HBgM...", "status": "failed", "timestamp": "1655287620", "recipient_id": "91XXXXXXXXXX", "errors": [{ "code": 131047, "title": "Message failed to send because more than 24 hours have passed since the customer last replied to this number", "href": "https://developers.facebook.com/docs/whatsapp/cloud-api/support/error-codes/" }] }] }, "field": "messages" }] }] }

errors[] carries the Meta error code; cross-reference the official error-codes table .

deleted

Light envelope (no conversation / pricing):

{ "object": "whatsapp_business_account", "entry": [{ "id": "3130247400631305", "changes": [{ "value": { "messaging_product": "whatsapp", "metadata": { "...": "..." }, "statuses": [{ "id": "wamid.HBgM...", "status": "deleted", "timestamp": "1655287862", "recipient_id": "91XXXXXXXXXX" }] }, "field": "messages" }] }] }

3. Inbound messages

Forwarded verbatim from Meta. entry[].changes[].value.messages[].type tells you the kind:

typeMessage contains
texttext.body — plain text reply
reactionreaction.message_id + reaction.emoji (≤ 30 days old)
image / video / audio / document / sticker<type>.id (media handle) + mime_type + sha256
audio with voice: trueVoice note (mime_type: "audio/ogg; codecs=opus")
locationlocation.latitude + location.longitude
contactscontacts[] with name + phones
interactive (button_reply / list_reply)The button or list item the user tapped
buttonQuick-reply payload from a template message
orderCatalog product order; carries order.product_items[]
systemService notifications (e.g. user_changed_number)
unknownUnsupported type with an errors[] description

Example — text message:

{ "object": "whatsapp_business_account", "entry": [{ "id": "2427770783922677", "changes": [{ "value": { "messaging_product": "whatsapp", "metadata": { "display_phone_number": "91XXXXXXXXXX", "phone_number_id": "586727755839684" }, "contacts": [{ "profile": { "name": "Pinnacle" }, "wa_id": "91XXXXXXXXXX" }], "messages": [{ "from": "91XXXXXXXXXX", "id": "wamid.HBgM...", "timestamp": "1655526425", "type": "text", "text": { "body": "Test message" } }] }, "field": "messages" }] }] }

Example — interactive list reply with context:

{ "messages": [{ "context": { "from": "91XXXXXXXXXX", "id": "wamid.HBgM..." }, "from": "91XXXXXXXXXX", "id": "wamid.HBgM...", "timestamp": "1655538521", "type": "interactive", "interactive": { "type": "list_reply", "list_reply": { "id": "id_1", "title": "one" } } }] }

Example — click-to-WhatsApp ad referral:

{ "messages": [{ "referral": { "source_url": "AD_OR_POST_FB_URL", "source_id": "ADID", "source_type": "ad", "headline": "AD_TITLE", "body": "AD_DESCRIPTION", "media_type": "image", "image_url": "RAW_IMAGE_URL", "thumbnail_url":"RAW_THUMBNAIL_URL" }, "from": "SENDER_PHONE", "id": "wamid.ID", "timestamp": "TIMESTAMP", "type": "text", "text": { "body": "BODY" } }] }

Delivery semantics

  • Fire-and-forget. Meta gets a 200 OK from us regardless of what your endpoint returns. No retries on this surface — keep your endpoint healthy or rely on idempotent processing keyed on statuses[].id / messages[].id.
  • 10 s timeout. Acknowledge fast, process async.
  • Order is not guaranteed. Multiple status callbacks for the same wamid can arrive out of order. Always trust the latest statuses[].timestamp.
  • Bandwidth. Media isn’t included inline — you receive the Meta media id and sha256. Use the WhatsApp media endpoints to download.

Node handler example

app.post("/webhooks/splashify", express.json(), async (req, res) => { const body = req.body; if (body.event === "WABA_ONBOARDED") { await db.customers.markOnboarded(body.waba_id, body.phone_number_id); return res.status(200).end(); } for (const entry of body.entry ?? []) { for (const ch of entry.changes ?? []) { const v = ch.value ?? {}; for (const s of v.statuses ?? []) { await db.outbound.updateStatus(s.id, s.status, s); } for (const m of v.messages ?? []) { await db.inbound.persist({ from: m.from, phone_number_id: v.metadata?.phone_number_id, ...m, }); } } } res.status(200).end(); });