# WhatsApp document messages

A document message carries a public URL WhatsApp fetches at send time, with an optional caption and an optional filename. It's the largest media arm, and the only one that carries both a caption and a filename.

## Send a document

Set `document.url`:

<!-- bird:tabs typescript,python,go,php,cli,curl -->

```typescript
const msg = await bird.whatsapp.send({
  to: "+16505551234",
  from: "+13124495648",
  document: { url: "https://cdn.example.com/invoices/a1b2c3.pdf" },
});
console.log(msg.id, msg.status);
```

```python
msg = client.whatsapp.send(
    to="+16505551234",
    from_="+13124495648",
    document={"url": "https://cdn.example.com/invoices/a1b2c3.pdf"},
)
print(msg.id, msg.status)
```

```go
package main

import (
	"context"
	"fmt"
	"log"
	"os"

	bird "github.com/messagebird/bird-sdk-go"
	"github.com/messagebird/bird-sdk-go/option"
)

func main() {
	client, err := bird.NewClient(option.WithAPIKey(os.Getenv("BIRD_API_KEY")))
	if err != nil {
		log.Fatal(err)
	}
	msg, err := client.Whatsapp.Send(context.Background(), bird.WhatsappSendParams{
		To:   "+16505551234",
		From: "+13124495648",
		Document: &bird.WhatsAppDocumentSend{Url: "https://cdn.example.com/invoices/a1b2c3.pdf"},
	})
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(msg.Id, *msg.Status)
}
```

```php
$document = (new WhatsAppMessageSendRequestDocument())
    ->setUrl('https://cdn.example.com/invoices/a1b2c3.pdf');
$message = $bird->whatsapp->send(
    to: '+16505551234',
    from: '+13124495648',
    document: $document,
);
echo $message->getId(), ' ', $message->getStatus();
```

```cli
bird whatsapp send \
  --to +16505551234 \
  --from +13124495648 \
  --document https://cdn.example.com/invoices/a1b2c3.pdf
```

```curl
curl -X POST "https://us1.platform.bird.com/v1/whatsapp/messages" \
  -H "Authorization: Bearer $BIRD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
      "to": "+16505551234",
      "from": "+13124495648",
      "document": {
        "url": "https://cdn.example.com/invoices/a1b2c3.pdf"
      }
    }'
```

<!-- /bird:tabs -->

The full shape adds `caption` and `filename`:

```json
{
  "to": "+16505551234",
  "from": "+13124495648",
  "document": {
    "url": "https://cdn.example.com/invoices/a1b2c3.pdf",
    "caption": "Your invoice for order A1B2C3",
    "filename": "invoice-a1b2c3.pdf"
  }
}
```

`from` is required on every service message: a number your workspace owns, not a Bird-managed one.

## Limits

| Field      | Bound                                                                                                                             | Enforced by                                                                                 |
| ---------- | --------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| File size  | 100 MB                                                                                                                            | WhatsApp only, at fetch (async)                                                             |
| File type  | PDF, Word, Excel, PowerPoint, or plain text render reliably in the WhatsApp client; other types are transmitted but not supported | WhatsApp only, at fetch (async)                                                             |
| `caption`  | up to 1024 characters                                                                                                             | Bird, at accept (`422`)                                                                     |
| `filename` | 1 to 100 characters                                                                                                               | Bird, at accept (`422`); this cap is Bird's own, since WhatsApp documents no filename limit |
| `url`      | absolute, `https`, has a host, no raw space                                                                                       | Bird, at accept (`422`)                                                                     |

Bird checks the URL's shape and the caption's and filename's length before anything is enqueued. It does not check the file's actual size or type; only WhatsApp's own fetch at send time can. See the hub's [sending media by URL](/docs/guides/whatsapp/message-types#sending-media-by-url) and [when media fails](/docs/guides/whatsapp/message-types#when-media-fails).

## Reading an inbound document

An inbound document carries the same `document` object, plus an `id` and `mime_type` Bird learned by fetching the file:

```json
{
  "id": "wam_01kya19eknftrs2s6p82asmvnh",
  "direction": "inbound",
  "from": { "phone_number": "+14155550100" },
  "to": { "phone_number": "+13124495569" },
  "document": {
    "id": "waf_01kyb2m4xq7whs0d8n3prv6tez",
    "url": "https://platform.bird.com/v1/whatsapp/messages/wam_01kya19eknftrs2s6p82asmvnh/media/waf_01kyb2m4xq7whs0d8n3prv6tez",
    "mime_type": "application/pdf",
    "caption": "Signed contract",
    "filename": "contract.pdf"
  },
  "status": "received"
}
```

`id` and `mime_type` are absent on an outbound read-back, since Bird never fetched the file it sent. To fetch the bytes behind an inbound document, see [Receiving WhatsApp messages](/docs/guides/whatsapp/receiving-whatsapp).

## Limits and failure modes

- **The customer service window has to be open.** Documents are service messages, deliverable only inside an open window; see the hub's [customer service window](/docs/guides/whatsapp/message-types#the-customer-service-window).
- **Bird rejects `http`; WhatsApp itself would fetch it.** See the hub's [sending media by URL](/docs/guides/whatsapp/message-types#sending-media-by-url) for the full shape check.
- **A rejected fetch still gets charged, and this is the arm most likely to run into it.** At 100 MB, a document is the largest thing you can send, and Bird checks nothing about the actual bytes at accept. See the hub's [when media fails](/docs/guides/whatsapp/message-types#when-media-fails) for `media_rejected` and the charge-on-failure fact. Document's own rejection text from WhatsApp hasn't been independently measured the way image's has, so treat the mapping as inferred by symmetry rather than confirmed per cause.
- **Omitting `filename` doesn't mean the recipient sees no name.** WhatsApp derives one from the URL's path instead, which can be an opaque hash or slug rather than something readable. Set `filename` explicitly to control what actually shows.
- **The 100-character `filename` cap is Bird's own choice, not a WhatsApp limit.** WhatsApp documents no filename length limit at all.
- **WhatsApp caches a fetched URL for about 10 minutes.** Resending the identical URL inside that window re-serves the first fetch; vary the URL to force a fresh one.

## Next steps

- [WhatsApp service messages](/docs/guides/whatsapp/message-types): the customer service window and the model every service message shares
- [Images](/docs/guides/whatsapp/message-types/images): for a photo or graphic instead of a file
- [Templates](/docs/guides/whatsapp/templates): for messages you can send once the window is closed
- [How sending works](/docs/guides/whatsapp/sending-whatsapp): the request envelope, the `202` model, and safe retries