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

# Send a PDF attachment

> A template that opens with a PDF: an invoice, a contract, an attendance sheet, a ticket. Make it once, wait for Meta's approval, then send each customer their own file.

A WhatsApp message carries a PDF as its attachment when its template opens with a document. The file can be anything a customer should keep: an invoice after a payment, a contract to sign, a month's attendance sheet, a booking's ticket, an account statement. The template is made once and approved by Meta; each message then carries its own PDF, from a link your server gives at send time. The customer sees the document at the top of the message, with its file name, and the text below it.

A file tied to something the customer did or holds, such as a payment, a contract or a booking, is a **utility** template. A brochure or a catalogue that promotes is **marketing**, which Meta delivers only to customers who opted in.

## 1. Make the template

<Tabs>
  <Tab title="In the console">
    On the application's **Notifications → Templates** page, press **New template**, choose the category, and under **Header type** choose **Document**. Upload a sample PDF: Meta reviews the template with it, and it is never sent to a customer. Write the body, for example:

    ```text theme={"dark"}
    مرحبًا {{name}}، أرفقنا لك {{document}} في هذه الرسالة.
    ```

    Meta refuses a body that starts or ends with a placeholder, so keep words before the first one and after the last.
  </Tab>

  <Tab title="Over the API (connected app)">
    Creating a template over the API is open to the key of [a connected app](/integrations/connected-apps) alone; a business's own key writes its templates in the console. Send `header_format: "DOCUMENT"` and `header_sample_url`, an https link to a sample PDF. Tawked downloads the sample once from that public address (at most 3 redirects, 8 seconds, at most 5 MB) and hands it to Meta for the review.

    ```bash theme={"dark"}
    curl -X POST https://tawked.com/v1/whatsapp/templates \
      -H "Authorization: Bearer $ACCESS_TOKEN" \
      -H "Content-Type: application/json" \
      -H "Idempotency-Key: document_attached-ar" \
      -d '{
        "name": "document_attached",
        "language": "ar",
        "category": "UTILITY",
        "header_format": "DOCUMENT",
        "header_sample_url": "https://example.com/samples/contract-sample.pdf",
        "body": "مرحبًا {{name}}، أرفقنا لك {{document}} في هذه الرسالة.",
        "examples": { "body:name": "سارة", "body:document": "عقد الاشتراك" }
      }'
    ```

    ```json theme={"dark"}
    { "id": "7100", "name": "document_attached", "language": "ar", "category": "UTILITY", "status": "PENDING" }
    ```

    Leave `header` out: a template that opens with a document has no header text. A refused sample answers `400 invalid_request` with `fields` naming `header_sample_url` and one of these codes in `errors`:

    | Code | Why |
    | - | - |
    | `headerSampleMissing` | No `header_sample_url` on a creation with `IMAGE` or `DOCUMENT`. |
    | `headerSampleUnreachable` | Tawked could not read the link: not https, a private address, an error status, a timeout. |
    | `headerSampleInvalid` | The file is not a PDF. |
    | `headerSampleTooLarge` | The file is over 5 MB. |

    A `header_format` other than `TEXT`, `IMAGE` or `DOCUMENT` answers `400 invalid_request` with `headerFormatInvalid`.
  </Tab>
</Tabs>

## 2. Wait for Meta's approval

Meta reviews the template; until it approves, a send answers `422 template_not_approved`. The list shows the status, and your code reads it with `GET /v1/whatsapp/templates/{template_id}` until it is `APPROVED`. A `REJECTED` template carries Meta's reason in `rejected_reason`. See [Templates](/whatsapp/templates).

## 3. Send it when the file is ready

When your system has the file, a payment's invoice, a signed contract or the month's attendance sheet, make the PDF reachable at a link and send the template with that link in `header.document.link` and the file's own name in `header.document.filename`. Use one `Idempotency-Key` per file, so a retry never sends it twice.

<CodeGroup>
  ```bash cURL theme={"dark"}
  curl -X POST https://tawked.com/v1/whatsapp/messages \
    -H "Authorization: Bearer $TAWKED_KEY" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: contract-1042" \
    -d '{
      "to": "0551234567",
      "template": "document_attached",
      "lang": "ar",
      "params": { "name": "سارة", "document": "عقد الاشتراك" },
      "header": {
        "document": {
          "link": "https://example.com/files/contract-1042.pdf?expires=1792051200&signature=4f1c",
          "filename": "Contract-1042.pdf"
        }
      },
      "reference": "contract-1042"
    }'
  ```

  ```javascript Node.js theme={"dark"}
  // When a file is ready for a customer: an invoice, a contract, an attendance sheet.
  async function sendPdf({ id, phone, name, title, fileKey, fileName }) {
    const link = await signedFileLink(fileKey, { minutes: 10 }); // your storage's signed link
    const res = await fetch('https://tawked.com/v1/whatsapp/messages', {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${process.env.TAWKED_KEY}`,
        'Content-Type': 'application/json',
        'Idempotency-Key': `file-${id}`,
      },
      body: JSON.stringify({
        to: phone,
        template: 'document_attached',
        lang: 'ar',
        params: { name, document: title },
        header: { document: { link, filename: fileName } },
        reference: `file-${id}`,
      }),
    });
    if (!res.ok) throw new Error(`Tawked answered ${res.status}: ${await res.text()}`);
    return (await res.json()).id; // keep it next to the file
  }
  ```
</CodeGroup>

The answer is `202 Accepted`, like any send, and the status follows in `message.*` webhooks (see [Sending messages](/whatsapp/messages#statuses-and-webhooks)). The message is charged and logged like any other template message, and its record keeps the link and the file name it was sent with.

| Field | Rule |
| - | - |
| `header.document.link` | Required for a template that opens with a document. An https link, at most 2048 characters, no spaces, to a PDF. |
| `header.document.filename` | Optional. 1 to 240 characters, no control characters, no `/` or `\`. The name the customer sees on the document. |

## Tips

* **Give a short-lived signed link.** Meta downloads the file from the link when it sends the message, so the link must open without a sign-in at that moment. An invoice, a contract or an attendance sheet holds personal data: a signed link that lives a few minutes is enough, and nobody can open it afterwards.
* **One link per file.** Meta may keep a copy of the file it downloaded and reuse it for a later message to the same link, by the link's `Cache-Control`, `Last-Modified` and `ETag` headers or by its own rules when there are none. Never put a new file behind a link you already sent; a signed link per file is new every time.
* **Serve it as a PDF.** Answer Meta's download with `Content-Type: application/pdf`, straight from the link: a sign-in page or an HTML page in its place makes the message fail.
* **Name the file.** Without `filename`, WhatsApp shows a generic name. Pass the file's own name, ending in `.pdf`, such as `Contract-1042.pdf`: WhatsApp picks the file's icon from the extension.
* **One PDF per message.** Each message carries one document, its own. The sample you gave with the template is only for Meta's review.
* **Meta's limit for a document is 100 MB.** A link Meta cannot download from makes the message fail, with Meta's reason in `error`.
* **One template for many files.** A placeholder in the body, such as `{{document}}`, names what is attached, so one approved template carries an invoice today and a contract tomorrow. A connected app makes utility templates only.

## Errors to expect

| Answer | Why |
| - | - |
| `422 invalid_params` | The template opens with a document and the send has no `header.document.link`; the link is not https or longer than 2048 characters; a `header.document` went to a template that does not open with a document, or a `header.image` to one that does. `message` names the field. |
| `400 invalid_request` | `header` is not of a shape the endpoint reads: `document` not an object, `link` not a string, extra keys, `text` beside `document`. |
| `422 template_not_approved` | Meta has not approved the template yet. |
| `422 unsupported_template` | The template opens with a video or a location, which the endpoint does not send yet. |

Every code, with what to do, is on [Errors](/errors).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.