Parsing Inbound Payloads
How to parse the JSON payload Meta sends to your webhook for each inbound message type — text, image, video, audio, document, and interactive button/list replies.
On this page
Every inbound message Meta sends to your webhook has the same outer wrapper. Inside that wrapper, the content differs by message type. Parsing means digging into the JSON to extract what matters — who sent it, what type it is, and what the content is.
The outer wrapper — always the same#
body.entry[0].changes[0].value.messages[0]
↑
the actual message objectYour server always digs to messages[0] first, then checks message.type:
const changes = body.entry?.[0]?.changes?.[0]?.value;
if (!changes?.messages) return; // delivery status, not a message
const message = changes.messages[0];
const from = message.from; // sender's phone number e.g. "919959623255"
const type = message.type; // "text", "image", "audio", etc.Per-type parsing#
Text message
// { "type": "text", "text": { "body": "Hello" } }
const text = message.text.body; // → "Hello"Image message
// { "type": "image", "image": { "id": "1234567890", "caption": "..." } }
const mediaId = message.image.id; // use Media API to download
const caption = message.image.caption; // optional, may be undefinedAudio / voice note
const mediaId = message.audio.id;Video
const mediaId = message.video.id;
const caption = message.video.caption;Document / file
// { "type": "document", "document": { "id": "111222333", "filename": "invoice.pdf" } }
const mediaId = message.document.id;
const filename = message.document.filename;Interactive — button tap
const id = message.interactive.button_reply.id; // → "yes_button"
const title = message.interactive.button_reply.title; // → "Yes"Interactive — list row selection
const id = message.interactive.list_reply.id;
const title = message.interactive.list_reply.title;Why media gives you an ID, not the file#
Meta never sends the actual file in the webhook — only the id. To get the file, call the
Media API separately:
GET https://graph.facebook.com/v21.0/{media-id}
→ returns a download URL
→ download the file from that URLSee Media API for the full download flow.
Testing with Postman#
You can simulate any inbound message type without a real phone by sending a POST to your webhook URL:
POST http://localhost:3003/webhook
Content-Type: application/json
{
"object": "whatsapp_business_account",
"entry": [{ "changes": [{ "value": {
"messages": [{
"from": "919959623255",
"type": "text",
"text": { "body": "Hello from Postman" }
}]
}}]}]
}Your server processes it exactly as if it came from a real customer.
Frequently asked#
How do I get the text a customer sent me?
Check message.type === 'text', then read message.text.body.
How do I get an image a customer sent me?
Read message.image.id from the payload, then call the Media API with that ID to get a
download URL for the actual file. See Media API.
How do I know which button the customer tapped?
Check message.type === 'interactive'. For a button tap, read
message.interactive.button_reply.id — this matches the id you set when you sent the
button message.
What if changes.messages is missing?
The webhook also fires for delivery/read status updates, which have statuses instead of
messages. Always guard with if (!changes?.messages) return before parsing.
Gotchas & common mistakes#
Was this page helpful?
Still stuck? Open a support ticket and our team will help.