Receiving WhatsApp documents
A PDF, spreadsheet, or any other file a contact attaches arrives as an inbound message carrying document: the shared media reference, plus the name their own device gave the file.
What an inbound document carries
Esempio di codice
{
"id": "wam_01kyd6s0qfxv8t4k7n1yze6rhc",
"direction": "inbound",
"from": { "phone_number": "+14155550100" },
"to": { "phone_number": "+13124495569" },
"status": "received",
"document": {
"id": "waf_01kyd5p6zs9yju2f0p5rtx8vgb",
"url": "https://platform.bird.com/v1/whatsapp/messages/wam_01kyd6s0qfxv8t4k7n1yze6rhc/media/waf_01kyd5p6zs9yju2f0p5rtx8vgb",
"mime_type": "application/pdf",
"filename": "invoice-A1B2C3.pdf"
},
"created_at": "2026-08-25T09:15:02Z"
}| Field | What it carries |
|---|---|
| id | The stored file, to pass as media_id when fetching the bytes |
| url | A Bird URL, fetched with your API key |
| mime_type | The media type WhatsApp reported for the file, such as application/pdf |
| filename | The sender's own name for the file; absent when the message carried none |
| caption | The text the contact typed under the document; absent when they sent none |
filename is the one field the sending side and the receiving side use differently: on a send it is the name you want shown in the chat, and on an inbound message it is whatever the contact's device supplied.
Fetching the bytes
Pass the message ID and the media id to the channel's media method. The hub's fetching inbound media carries that call in every language, along with the redirect and header rules it follows, and owns the retention window the file lives in.
Do not write filename to disk as it arrives. It is attacker-controlled text from an unauthenticated sender: it can carry path separators, traversal sequences, a misleading second extension, or a name that collides with a file you already hold. Generate your own storage key from the media id, keep filename as a display label, and decide what to do with the file from mime_type rather than from the extension the name claims.
The webhook payload
whatsapp.received carries the document arm on the event envelope:
Esempio di codice
{
"type": "whatsapp.received",
"timestamp": "2026-08-25T09:15:02.336Z",
"data": {
"whatsapp_id": "wam_01kyd6s0qfxv8t4k7n1yze6rhc",
"workspace_id": "ws_01ky7m235keycbnwyajabe1a6b",
"direction": "inbound",
"from": { "phone_number": "+14155550100" },
"to": { "phone_number": "+13124495569" },
"document": {
"id": "waf_01kyd5p6zs9yju2f0p5rtx8vgb",
"url": "https://platform.bird.com/v1/whatsapp/messages/wam_01kyd6s0qfxv8t4k7n1yze6rhc/media/waf_01kyd5p6zs9yju2f0p5rtx8vgb",
"mime_type": "application/pdf",
"filename": "invoice-A1B2C3.pdf"
},
"tags": null,
"metadata": null
}
}A claims or onboarding flow that collects paperwork can route on mime_type here and queue the fetch, since the bytes stay available for the whole retention window.
Things to watch
- mime_type is WhatsApp's report on the file, and says nothing about what is inside it. Scan anything you accept from a contact, and validate the file's own structure before parsing it.
- A caption and a filename are different fields. A contact who types a note when attaching the file fills caption; filename still comes off their device.
Next steps
- How receiving works: the inbound envelope, media fetching, and the whatsapp.received webhook
- WhatsApp document messages: the send side of the same arm
- Receiving images: the same media shape for a photo of a document
- WhatsApp events: the full event list, over the API or webhooks