# 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

```json
{
  "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](/docs/guides/whatsapp/receiving-whatsapp#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:

```json
{
  "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](/docs/guides/whatsapp/receiving-whatsapp): the inbound envelope, media fetching, and the `whatsapp.received` webhook
- [WhatsApp document messages](/docs/guides/whatsapp/message-types/documents): the send side of the same arm
- [Receiving images](/docs/guides/whatsapp/receiving-whatsapp/images): the same media shape for a photo of a document
- [WhatsApp events](/docs/guides/whatsapp/events): the full event list, over the API or webhooks