Skip to content

Concept map· 6 connected terms

Webhook Payload

Also called: Webhook envelope · Webhook body · Notification payload · entry and changes

In one line
The JSON body of every WhatsApp webhook: an envelope of object, entry and changes wrapping the field-specific value your code actually reads.

Reviewed

What is a webhook payload?

A webhook payload is the JSON body Meta POSTs to your callback URL. Whatever the event — an inbound message, a delivery status, a template approval, an account alert — it arrives in the same envelope: object set to whatsapp_business_account, an entry array whose items each carry the WhatsApp Business Account ID in id, and inside each entry a changes array whose items name one of your subscribed webhook fields in field and carry a value.

The envelope is generic; the value is not. For the messages field, value holds messaging_product, a metadata object with display_phone_number and phone_number_id, and then contacts and messages for inbound traffic, statuses for updates on messages you sent, or an errors array. For fields such as message_template_status_update or account_update, value has a completely different shape, with keys like event and reason. Your code therefore branches on field first and on the keys present in value second.

This page is about the envelope and how to walk it safely. The objects inside value each have their own page: the messages webhook for inbound message types, the statuses webhook for updates on what you sent, webhook error objects, and the webhook contacts object that identifies the sender. Meta says payloads can be up to 3 MB and that it aggregates up to 1,000 updates into one POST — though batching is not guaranteed — and every POST is signed in X-Hub-Signature-256 over the raw bytes, so you parse only after verifying.

Key takeaways

  • A webhook payload is the JSON body of every WhatsApp webhook: object, entry, changes, then a field-specific value.
  • object is always whatsapp_business_account, entry[].id is the WABA ID and changes[].field names the field that fired.
  • Meta batches up to 1,000 updates into one POST, but batching is not guaranteed, so loop over every entry and change.
  • Payloads can be up to 3 MB; verify X-Hub-Signature-256 over the raw bytes before parsing them.
  • Branch on field first, then on the keys present in value: messages, statuses or errors.

Walk the envelope, layer by layer

9 / 9
{
"object": "whatsapp_business_account",
"entry": [
{
"id": "102290129340398",
"changes": [
{
"field": "messages",
"value": {
"messaging_product": "whatsapp",
"metadata": {
"display_phone_number": "919876543210",
"phone_number_id": "106540352242922"
},
"statuses": [
{
"id": "wamid.HBgMOTE5ODEyMzQ1Njc4FQIAERgSMDQ0QjU3RkM2",
"status": "delivered",
"timestamp": "1760850000",
"recipient_id": "919812345678"
}
]
}
}
]
},
{
"id": "102290129340398",
"time": 1760850012,
"changes": [
{
"field": "message_template_status_update",
"value": {
"event": "APPROVED",
"message_template_id": 1189056642410321,
"message_template_name": "diwali_catalogue_2026",
"message_template_language": "en",
"reason": "NONE"
}
}
]
}
]
}
One trimmed POST carrying two entries: a delivered status and a template approval. Step through the layers every handler walks, and watch value change shape when field changes.

Field 9: entry.1.changes.0.value.event — Template events use event and reason instead of messages or statuses.

How does a webhook payload work?

  1. Verify, then parse

    Check X-Hub-Signature-256 against the raw body before calling JSON.parse, then reject anything whose object is not whatsapp_business_account.

  2. Loop over every entry

    Each entry item belongs to one WhatsApp Business Account, identified by entry[].id. Template and account events also carry a Unix time on the entry.

  3. Loop over every change and read its field

    changes[].field names the subscription that fired — messages, message_template_status_update, account_update and so on. Dispatch to one handler per field.

  4. Inspect value by its keys

    Inside a messages change, value.metadata.phone_number_id tells you which business number is involved; then check which arrays exist — messages (inbound), statuses (outbound updates) or errors.

  5. Ignore what you do not know

    Log unknown fields and keys, return 200 anyway and move on. New fields appear over time, and a handler that throws on them only triggers retries.

A webhook payload in code

type Change = { field: string; value: Record<string, any> };
type Entry = { id: string; time?: number; changes: Change[] };
type Payload = { object: string; entry: Entry[] };
export function walk(payload: Payload) {
if (payload.object !== "whatsapp_business_account") return;
for (const entry of payload.entry ?? []) {
const wabaId = entry.id;
for (const { field, value } of entry.changes ?? []) {
switch (field) {
case "messages": {
const phoneNumberId = value.metadata?.phone_number_id;
for (const msg of value.messages ?? []) onInbound(wabaId, phoneNumberId, msg, value.contacts);
for (const st of value.statuses ?? []) onStatus(wabaId, phoneNumberId, st);
for (const err of value.errors ?? []) onSystemError(wabaId, err);
break;
}
case "message_template_status_update":
onTemplateEvent(wabaId, value); // { event, message_template_id, reason, ... }
break;
default:
log.info("unhandled webhook field", { field }); // never throw
}
}
}
}

Iterate both arrays, dispatch on field, then on the keys inside value — never on position.

Common wrong turns

Common mistakes with a webhook payload

  • Reading only the first entry and change

    entry[0].changes[0] works in testing and fails in production when Meta batches. Always iterate both arrays, and every item inside messages or statuses.

  • Assuming a messages change means a message

    The messages field carries inbound messages, outbound statuses and system errors. Check which arrays are present before reading messages[0].

  • Closed schemas that reject new keys

    Validating against a strict schema turns every new Meta key into a failed webhook and a retry storm. Validate the keys you use and pass the rest through untouched.

  • Logging whole payloads with personal data

    Payloads include names, phone numbers and message text. Mask wa_id, from and message bodies before logging, and keep raw copies only briefly.

Key facts at a glance

A webhook payload: key facts and limits

object
Always whatsapp_business_account
entry[].id
The WhatsApp Business Account ID
changes[].field
The subscribed field that fired
changes[].value
Shape depends on the field
Maximum size
3 MB per payload (Meta)
Batching
Up to 1,000 updates per POST (not guaranteed)
Signature
X-Hub-Signature-256 over the raw body

Example: The approval that was already there

A Surat textile exporter subscribes to messages and message_template_status_update. One morning a single POST carries two entries: a delivered status for an order update and an APPROVED event for a new Diwali catalogue template. The current handler routes the first by value.statuses to the order timeline and the second by field to the template sync job. Its first version read only entry[0] — and the campaign team once spent a day waiting for a template approval that had already arrived and been silently dropped.

Why does a webhook payload matter?

Most webhook bugs are envelope bugs, not business-logic bugs: reading only entry[0].changes[0], assuming every messages change contains messages, or crashing on a field nobody planned for. A handler that walks every entry and change, branches on field and tolerates unknown keys keeps working when Meta adds fields or batches events — and it is the base every other webhook feature, from routing to deduplication, is built on.

Good practice

Best practices for a webhook payload

  • Use metadata.phone_number_id, not display_phone_number, as the key for the business number.
  • Persist each raw payload (masked) with the WABA ID and received time for replay and debugging.
  • Keep one fixture per subscribed field and run the walker against all of them in CI.
  • Return 200 for fields you have not built handlers for yet, so they do not pile up as retries.

Webhook Payload FAQs

Is the webhook payload different for each event type?+
Only the inner `value` differs; the envelope is the same for every event. That is why handlers branch on `field` before reading anything else, then check which keys `value` contains.
Can one webhook POST contain more than one message or status?+
Yes, Meta aggregates up to 1,000 updates into one POST. A request can carry several entries and changes, and a `messages` or `statuses` array can hold more than one item, so loop over all of them.
Why does my webhook handler only see the first message?+
Because it reads `entry[0].changes[0]` instead of looping. That works in testing, where events arrive one by one, and silently drops the rest when Meta batches several updates into one request.
What is the maximum size of a WhatsApp webhook payload?+
Up to 3 MB, according to Meta. Set your web server's and body parser's request size limits above that, or large batches fail with a 413 and Meta retries them.
Does the payload include media files?+
No, media messages carry a media ID and metadata only. You download the file separately with the Graph API, and Meta says media IDs received in webhooks expire after 7 days.

See InfiQ pricing

Try it on the official WhatsApp Business API

InfiQ is an official Meta Business Partner. Start a 7-day free trial — no card needed — and see this running on your own number.