> ## Documentation Index
> Fetch the complete documentation index at: https://docs.prelude.so/llms.txt
> Use this file to discover all available pages before exploring further.

# Send images, videos, and documents on WhatsApp

> Attach an image, a video, or a document to the header of a WhatsApp template message with the Notify API.

A WhatsApp template can carry a media header: one image, video, or document displayed above the message body. Notify sends it through the `document` field on the send endpoint.

The template is fixed, the media is per message. Register the template once, then pass a different URL on every send: this customer's invoice, this passenger's boarding pass, this week's product photo.

<Info>
  Media headers require a WhatsApp Business Account connected to Prelude. See the [WhatsApp BSP guide](/verify/v2/documentation/whatsapp) to connect yours.
</Info>

## How it works

<Steps>
  <Step title="Register the template with a media header">
    The WhatsApp template on your WABA needs a header component with the format `IMAGE`, `VIDEO`, or `DOCUMENT`, and its name must match your Prelude template ID. Your Customer Success Manager sets this up with you.
  </Step>

  <Step title="Meta approves the template">
    Prelude reads the header format from the approved template and stores it on the template. Until that sync lands, a send with `document` returns `document_not_supported`.
  </Step>

  <Step title="Send a media URL on every message">
    Pass `document.url` with each send. Prelude checks the URL against the registered header format and sends it as the matching WhatsApp parameter, so an image renders as an image and a video renders as a playable clip.
  </Step>
</Steps>

## Supported formats

The header format registered with Meta decides what you can send. You don't declare the media type in the request. Prelude resolves it from the template.

| Header format | Extensions Prelude accepts                 | `filename` | Meta size limit |
| ------------- | ------------------------------------------ | ---------- | --------------- |
| `IMAGE`       | `.png`, `.jpg`, `.jpeg`, `.webp`           | Ignored    | 5 MB            |
| `VIDEO`       | `.mp4`, `.3gp`                             | Ignored    | 16 MB           |
| `DOCUMENT`    | PDF and Office formats, no extension check | Required   | 100 MB          |

Size limits are Meta's, not ours. See [Meta's supported media types](https://developers.facebook.com/docs/whatsapp/cloud-api/reference/media/) for the full list.

## Sending a message with media

```json theme={null}
{
  "template_id": "template_01k8ap1btqf5r9fq2c8ax5fhc9",
  "to": "+33612345678",
  "preferred_channel": "whatsapp",
  "variables": { "order_id": "12345" },
  "document": {
    "url": "https://cdn.example.com/invoices/12345.pdf",
    "filename": "invoice-12345.pdf"
  }
}
```

<ParamField path="document.url" type="string" required>
  HTTPS URL of the media file. WhatsApp downloads it at send time, so it has to be publicly reachable.
</ParamField>

<ParamField path="document.filename" type="string">
  The filename WhatsApp displays to the recipient. Required for `DOCUMENT` headers, ignored for `IMAGE` and `VIDEO`.
</ParamField>

### SDK examples

<CodeGroup>
  ```javascript Node.js theme={null}
  const response = await client.notify.send({
    template_id: "template_01k8ap1btqf5r9fq2c8ax5fhc9",
    to: "+33612345678",
    preferred_channel: "whatsapp",
    variables: { order_id: "12345" },
    document: {
      url: "https://cdn.example.com/invoices/12345.pdf",
      filename: "invoice-12345.pdf",
    },
  });
  ```

  ```python Python theme={null}
  response = client.notify.send(
      template_id="template_01k8ap1btqf5r9fq2c8ax5fhc9",
      to="+33612345678",
      preferred_channel="whatsapp",
      variables={"order_id": "12345"},
      document={
          "url": "https://cdn.example.com/invoices/12345.pdf",
          "filename": "invoice-12345.pdf",
      },
  )
  ```

  ```go Go theme={null}
  response, err := client.Notify.Send(context.TODO(), prelude.NotifySendParams{
      TemplateID:       prelude.F("template_01k8ap1btqf5r9fq2c8ax5fhc9"),
      To:               prelude.F("+33612345678"),
      PreferredChannel: prelude.F(prelude.NotifySendParamsPreferredChannelWhatsapp),
      Variables:        prelude.F(map[string]string{"order_id": "12345"}),
      Document: prelude.F(prelude.NotifySendParamsDocument{
          URL:      prelude.F("https://cdn.example.com/invoices/12345.pdf"),
          Filename: prelude.F("invoice-12345.pdf"),
      }),
  })
  ```
</CodeGroup>

## URL requirements

The URL has to be fetchable by WhatsApp, without credentials, at the moment you send. Files behind an authenticated endpoint or on a private network fail at delivery, not at request time.

Prelude reads the file extension from the URL **path** and ignores the query string. A signed URL like `https://cdn.example.com/invoices/12345.pdf?token=abc123` passes. A URL with no extension in the path, like `https://cdn.example.com/files?id=12345`, is rejected with `invalid_document_media_type` for image and video templates.

<Note>
  The extension check catches obvious mismatches early, such as an `.mp4` on a template with an image header. WhatsApp enforces the final list of accepted file types and sizes.
</Note>

## Media becomes mandatory

Once a template has a media header, every send to that template needs a `document`. A send without one returns `document_required`. There is no text-only mode for a media template.

The reverse also holds: sending `document` to a template with no media header, or a text header, returns `document_not_supported`.

## Bulk sends

[Send bulk messages](/notify/v2/api-reference/send-bulk-messages) takes a single `document` shared by every recipient in the batch, and validates it per recipient. Use it for one campaign asset going out to a list. For per-recipient media, such as a personal invoice, send the messages individually.

## Channel fallback

The attachment only travels over WhatsApp. If Prelude falls back to SMS or RCS, the recipient gets the text of the message with no media. Set `preferred_channel` to `whatsapp` when the media carries the point of the message, and keep the body understandable on its own for the cases where it doesn't reach WhatsApp.

## Error handling

| Error Code                    | Description                                                              | Action                                                                           |
| ----------------------------- | ------------------------------------------------------------------------ | -------------------------------------------------------------------------------- |
| `document_required`           | The template has a media header and the request has no `document`        | Add a `document` object with a `url`                                             |
| `document_not_supported`      | The template has no media header                                         | Remove the `document` object, or check that Meta has approved the media template |
| `invalid_document_url`        | The URL is not a valid HTTP or HTTPS URL                                 | Send an absolute HTTPS URL with a hostname                                       |
| `invalid_document_media_type` | The extension in the URL path doesn't match the template's header format | Send a file type the header accepts                                              |
| `missing_document_filename`   | The template has a `DOCUMENT` header and `filename` is empty             | Add the filename to display to the recipient                                     |

For a complete list of errors, see the [Error documentation](/introduction/errors).

## Limitations

* Media headers apply to **outbound template messages** only. Replies sent with [Reply to an inbound message](/notify/v2/api-reference/send-a-reply) are text.
* Inbound WhatsApp media is not forwarded. See [2-Way Messaging with WhatsApp](/notify/v2/documentation/whatsapp).
* One media item per message, in the header. WhatsApp templates don't support media in the body.
* Prelude fills the header and body of a template. Dynamic button parameters, carousels, and location headers are not supported.

## Related documentation

* [Send a message](/notify/v2/api-reference/send-a-message) - The full request reference
* [WhatsApp BSP](/verify/v2/documentation/whatsapp) - Connect your WhatsApp Business Account
* [2-Way Messaging with WhatsApp](/notify/v2/documentation/whatsapp) - Receive inbound messages and reply
