Receiving WhatsApp Events via Webhook
Superfone can forward the WhatsApp events that arrive on your business number — incoming customer messages, delivery/read receipts, and other account events — to an HTTPS endpoint you control. Use this to feed your own systems: an AI agent, a CRM, a ticketing tool, or custom automation.
This is an outbound webhook. Superfone receives events from Meta on your behalf and relays them to your endpoint. It runs alongside the Superfone dashboard — it does not replace it.
How it works
- A customer messages your WhatsApp business number.
- Meta delivers the event to Superfone.
- Superfone forwards that event to each enabled webhook endpoint configured on your account.
The payload Superfone forwards is Meta's original WhatsApp Cloud API webhook payload, relayed as-is. You therefore code against Meta's standard webhook schema, documented below. For the authoritative field-level reference, see Meta's Webhooks Components reference.
Registering your endpoint
Webhook forwarding is configured per WhatsApp account. Each entry has:
| Field | Type | Description |
|---|---|---|
url | string | Your HTTPS endpoint that will receive the POST requests. |
enabled | boolean | Whether forwarding to this endpoint is active. |
events | string[] | Event categories (reserved for future use; all enabled events are currently forwarded). |
The configuration field that holds your HTTPS endpoint is named url in the system.
Just provide your HTTPS endpoint to the Superfone team, and we'll register it on your WhatsApp account so you start receiving the webhook at that endpoint. There is currently no self-serve API to manage webhook endpoints — registration is handled by Superfone.
Requirements for your endpoint:
- Must be reachable over HTTPS.
- Must accept an HTTP
POSTwith a JSON body. - Must respond
200quickly to acknowledge receipt.
The request Superfone sends
| Property | Value |
|---|---|
| Method | POST |
| Header | x-app-name: dragonfly — the only header Superfone sets; identifies the sender |
| Body | Meta's raw WhatsApp webhook payload, sent as JSON (see schema) |
x-app-name: dragonfly is the only header Superfone sets. Content-Type is not set
deliberately — the body is simply sent as JSON. There is no payload signature (no
X-Hub-Signature-256) and no hub.challenge verification handshake against your
endpoint — those apply only between Meta and Superfone, not on this hop. Don't treat the
request as authenticated; verify it on your side (for example, a secret token in the
endpoint path).
Payload schema
Every payload uses Meta's envelope:
object: "whatsapp_business_account"
entry[]
id // your WhatsApp Business Account ID
changes[]
field // "messages" (covers both inbound messages and statuses)
value
messaging_product // "whatsapp"
metadata
display_phone_number // your business number
phone_number_id // your phone number ID
contacts[] // sender profile (present for inbound messages)
profile.name
wa_id // sender's WhatsApp number
messages[] // present for inbound customer messages
statuses[] // present for delivery/read/failed receipts
Inbound text message
{
"object": "whatsapp_business_account",
"entry": [
{
"id": "<WHATSAPP_BUSINESS_ACCOUNT_ID>",
"changes": [
{
"field": "messages",
"value": {
"messaging_product": "whatsapp",
"metadata": {
"display_phone_number": "918888888888",
"phone_number_id": "1234567890"
},
"contacts": [
{ "profile": { "name": "Rahul" }, "wa_id": "919876543210" }
],
"messages": [
{
"from": "919876543210",
"id": "wamid.HBgMOTE5ODc2NTQzMjEwFQIAEhgU...",
"timestamp": "1700000000",
"type": "text",
"text": { "body": "Hi, is this available?" }
}
]
}
}
]
}
]
}
Inbound media message
{
"object": "whatsapp_business_account",
"entry": [
{
"id": "<WHATSAPP_BUSINESS_ACCOUNT_ID>",
"changes": [
{
"field": "messages",
"value": {
"messaging_product": "whatsapp",
"metadata": {
"display_phone_number": "918888888888",
"phone_number_id": "1234567890"
},
"contacts": [
{ "profile": { "name": "Rahul" }, "wa_id": "919876543210" }
],
"messages": [
{
"from": "919876543210",
"id": "wamid.HBgMOTE5ODc2NTQzMjEw...",
"timestamp": "1700000000",
"type": "image",
"image": {
"id": "<MEDIA_ID>",
"mime_type": "image/jpeg",
"sha256": "<hash>",
"caption": "optional caption"
}
}
]
}
}
]
}
]
}
media_id, not a downloadable URLFor media messages, the payload contains a Meta media_id, not a downloadable URL.
You cannot download this media_id yourself — it requires Superfone's authenticated Meta
credentials, and Meta's media links expire. Document, audio, video, and sticker messages use
the same shape under document / audio / video / sticker. (Delivering a directly
fetchable media URL is on the roadmap.)
Status update (sent / delivered / read / failed)
{
"object": "whatsapp_business_account",
"entry": [
{
"id": "<WHATSAPP_BUSINESS_ACCOUNT_ID>",
"changes": [
{
"field": "messages",
"value": {
"messaging_product": "whatsapp",
"metadata": {
"display_phone_number": "918888888888",
"phone_number_id": "1234567890"
},
"statuses": [
{
"id": "wamid.HBgMOTE5ODc2NTQzMjEw...",
"status": "delivered",
"timestamp": "1700000000",
"recipient_id": "919876543210",
"conversation": {
"id": "b1afb7b7d806c1c32fc190d5ff422331",
"origin": { "type": "service" }
},
"pricing": {
"billable": true,
"category": "service",
"pricing_model": "CBP"
}
}
]
}
}
]
}
]
}
status values are sent, delivered, read, failed, and deleted (the customer
deleted their message). The conversation and pricing objects are delivered on most
receipts. For failed, Meta includes an errors[] array — see Status object.
Field descriptions
Envelope
| Field | Type | Description |
|---|---|---|
object | string | Always "whatsapp_business_account". Identifies the payload as a WhatsApp Business Account event. |
entry[] | object[] | One entry per WhatsApp Business Account. May contain multiple entries — always iterate. |
entry[].id | string | Your WhatsApp Business Account (WABA) ID. |
entry[].changes[] | object[] | The list of changes (events) in this entry. May contain multiple changes — always iterate. |
entry[].changes[].field | string | The change topic. For messages and status receipts this is "messages". |
entry[].changes[].value | object | The event payload. Contains metadata, and either messages or statuses. |
value object
| Field | Type | Description |
|---|---|---|
messaging_product | string | Always "whatsapp". |
metadata.display_phone_number | string | Your business number in display form (e.g. 918888888888). |
metadata.phone_number_id | string | Your business number's ID. This is the routing key — use it to tell which number the event is for. |
contacts[] | object[] | Sender profile. Present for inbound messages; absent on status receipts. |
contacts[].wa_id | string | The customer's WhatsApp number (their ID on WhatsApp). |
contacts[].profile.name | string | The customer's WhatsApp profile (display) name. |
messages[] | object[] | Present for inbound customer messages. See Message object. |
statuses[] | object[] | Present for delivery/read/failed receipts. See Status object. |
Message object
Each item in messages[] describes one inbound message.
| Field | Type | Description |
|---|---|---|
id | string | Message ID (wamid). Globally unique — useful as a key to dedupe defensively if you need it. |
from | string | The sender's WhatsApp number (the customer). |
timestamp | string | Unix epoch seconds (as a string) when Meta received the message. |
type | string | The message type: text, image, document, audio, video, sticker, location, contacts, interactive, reaction, button, order, system, request_welcome, or unsupported. Tolerate types you don't handle. |
<type> | object | Type-specific content, keyed by the value of type (e.g. a text message has a text object, an image message has an image object). |
context | object | Optional. Present when the message is a reply or forward. Contains from and the id (wamid) of the message being replied to. |
Type-specific content
When type is | Field | Type | Description |
|---|---|---|---|
text | text.body | string | The message text. |
image / video / document / audio / sticker | <type>.id | string | Meta media_id. Not directly downloadable — see the media limitation. |
<type>.mime_type | string | MIME type of the media, e.g. image/jpeg. | |
<type>.sha256 | string | SHA-256 hash of the media file. | |
<type>.caption | string | Optional. Caption (image, video, document). | |
document.filename | string | Optional. Original filename (document only). | |
location | location.latitude, location.longitude | number | Coordinates shared by the customer. name and address may also be present. |
reaction | reaction.emoji | string | The emoji reaction. reaction.message_id is the wamid being reacted to. |
reaction.message_id | string | The wamid of the message the customer reacted to. | |
button | button.text, button.payload | string | A tap on a template quick-reply button. text is the button label; payload is the configured payload. |
interactive | interactive.type | string | A reply to an interactive message: button_reply or list_reply. |
interactive.button_reply | object | Present when interactive.type is button_reply. Contains id and title. | |
interactive.list_reply | object | Present when interactive.type is list_reply. Contains id, title, and optional description. |
Status object
Each item in statuses[] is a receipt for a message you sent.
| Field | Type | Description |
|---|---|---|
id | string | The wamid of the message this receipt is for. Match it to a message you previously sent. |
status | string | sent, delivered, read, failed, or deleted. |
timestamp | string | Unix epoch seconds (as a string) when the status changed. |
recipient_id | string | The customer's WhatsApp number the message was sent to. |
conversation | object | Usually present. The messaging conversation this receipt belongs to. Contains id and origin.type (e.g. service, marketing, utility, authentication). |
pricing | object | Usually present. Billing info for the conversation: billable (boolean), category, and pricing_model. |
errors[] | object[] | Present only when status is failed. See Error object. |
Error object
Each item in statuses[].errors[] describes why a message failed.
| Field | Type | Description |
|---|---|---|
code | number | Meta error code. Look it up in Meta's error-codes reference. |
title | string | Short error title, e.g. Message undeliverable. |
message | string | Optional. Human-readable error message (often equal to title). |
error_data.details | string | Optional. Additional detail about the failure. |
href | string | Optional. Link to the relevant Meta error-codes documentation. |
How your endpoint should behave
- Return
200immediately, then process asynchronously. Superfone does not retry, so a slow or failing response means the event is lost — keep your handler fast and do the heavy work after acknowledging. - Delivery is at-most-once. Superfone sends each event once and does not retry, so
if your endpoint is unreachable or returns a non-2xx, the event is missed (lost) — it
is never redelivered. Duplicates are unlikely and can only happen if Meta delivers the
same upstream event to Superfone more than once; dedupe by
messages[].id(wamid) as a defensive measure only. - Iterate
entry[] → changes[] → messages[] / statuses[]; handle batches. - Branch on
messagesvsstatuses— you'll receive both. - Tolerate event types you don't handle (you may also receive non-message account events). Ignore what you don't need.
Further reading
The payload Superfone forwards is Meta's standard WhatsApp Cloud API webhook payload. For the full, authoritative schema of every message and status type, refer to Meta's official Webhooks Components reference.
You don't need to set up webhooks on Meta yourself — Superfone handles registration with Meta. The link above is only for understanding the payload fields you'll receive.