# Send a template with a file in its header

`POST /v1/messages`

**This is the most common use of a utility template: sending the invoice.** A Meta template can carry a **header** with a document, an image or a video, and that file is chosen **on every send** — the approved template fixes the TYPE, you supply the file.

**How to send it.** Like any template (POST /v1/messages with `type: "template"`), except the `components` array carries a `header` component **first**, with the file:

```
{ "type": "header", "parameters": [ { "type": "document", "document": { "link": "https://…/A-1042.pdf", "filename": "Invoice A-1042.pdf" } } ] }
```

**Where the file comes from: a link of yours.** Meta accepts `link` (a public **https** URL **Meta** downloads) or `id` (a media id). **Connect stores no files and has no upload endpoint**, so the only ids that exist are those of files you RECEIVED — to send an invoice of your own, **host it and send the link**. Same rule as "Send media", for the same reason: we don't keep content.

⚠ **The parameter type is dictated by the TEMPLATE, not the file.** If Meta approved the header as `DOCUMENT`, the parameter goes as `document` even if the file is a JPG. Sending `image` on a `DOCUMENT` template is rejected by Meta.

⚠ **`filename` is documents only** (it's the name the recipient sees). On image and video Meta rejects it.

🔴 **What you CAN'T do: CREATE a media-header template from Connect.** Only **text** headers can be built here. If you send a `header.format` of image/document/video to `POST /v1/templates` you get `400 TEMPLATE_PRECHECK_FAILED` with code `TPL_HEADER_FORMAT_UNSUPPORTED` — **we don't create it half-way and we don't tell you it worked**. The path is: create it in **Meta Business Manager** (which does accept media headers) and then `POST /v1/templates/sync` — Connect reads it correctly, lists it, notifies you when its status changes, and sends it as above.

## Examples

### cURL

```bash
curl -X POST https://api.waiaconnect.com/v1/messages \
  -H "Authorization: Bearer wc_live_YOUR_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"connectionId":"conn_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX","to":"5493511234567","type":"template","template":{"name":"envio_factura","language":"es","components":[{"type":"header","parameters":[{"type":"document","document":{"link":"https://your-server.example/invoices/A-1042.pdf","filename":"Factura A-1042.pdf"}}]},{"type":"body","parameters":[{"type":"text","text":"María"}]}]}}'
# The header component goes FIRST and its type is the one the APPROVED TEMPLATE
# declares (document | image | video) — NOT the file's mime type.
# image: {"type":"image","image":{"link":"https://…/photo.jpg"}}   (no filename)
# by media id instead of a link: {"document":{"id":"1234567890"}}
# ⚠ You cannot CREATE a media-header template from Connect (only TEXT headers).
#   Create it in Meta Business Manager, then POST /v1/templates/sync.
```

### Node.js

```javascript
// The header component goes FIRST; its type is the one the APPROVED TEMPLATE declares
// (document | image | video), not the file's mime. `filename` applies to documents only.
const res = await fetch("https://api.waiaconnect.com/v1/messages", {
  method: "POST",
  headers: {
    "Authorization": "Bearer wc_live_YOUR_API_KEY",
    "Idempotency-Key": crypto.randomUUID(),
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
  "connectionId": "conn_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
  "to": "5493511234567",
  "type": "template",
  "template": {
    "name": "envio_factura",
    "language": "es",
    "components": [
      {
        "type": "header",
        "parameters": [
          {
            "type": "document",
            "document": {
              "link": "https://your-server.example/invoices/A-1042.pdf",
              "filename": "Factura A-1042.pdf"
            }
          }
        ]
      },
      {
        "type": "body",
        "parameters": [
          {
            "type": "text",
            "text": "María"
          }
        ]
      }
    ]
  }
})
});
console.log(await res.json()); // { id: "msg_…", status: "queued" }
```

### PHP

```php
<?php
// The header component goes FIRST; its type is the one the APPROVED TEMPLATE declares.
$ch = curl_init("https://api.waiaconnect.com/v1/messages");
curl_setopt_array($ch, [
  CURLOPT_POST => true,
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => [
    "Authorization: Bearer wc_live_YOUR_API_KEY",
    "Idempotency-Key: " . bin2hex(random_bytes(16)),
    "Content-Type: application/json",
  ],
  CURLOPT_POSTFIELDS => json_encode(["connectionId" => "conn_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX","to" => "5493511234567","type" => "template","template" => ["name" => "envio_factura","language" => "es","components" => [["type" => "header","parameters" => [["type" => "document","document" => ["link" => "https://your-server.example/invoices/A-1042.pdf","filename" => "Factura A-1042.pdf"]]]],["type" => "body","parameters" => [["type" => "text","text" => "María"]]]]]]),
]);
echo curl_exec($ch);
```

### Python

```python
import requests, uuid
# The header component goes FIRST; its type is the one the APPROVED TEMPLATE declares
# (document | image | video), not the file's mime. "filename" applies to documents only.
res = requests.post("https://api.waiaconnect.com/v1/messages",
  headers={
    "Authorization": "Bearer wc_live_YOUR_API_KEY",
    "Idempotency-Key": str(uuid.uuid4()),
  },
  json={"connectionId":"conn_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX","to":"5493511234567","type":"template","template":{"name":"envio_factura","language":"es","components":[{"type":"header","parameters":[{"type":"document","document":{"link":"https://your-server.example/invoices/A-1042.pdf","filename":"Factura A-1042.pdf"}}]},{"type":"body","parameters":[{"type":"text","text":"María"}]}]}})
print(res.json())
```

## Notes

- **How to tell whether a template needs a file:** `GET /v1/templates` returns `header: { format: "DOCUMENT" }` (or `IMAGE`/`VIDEO`). In the panel they're marked with 📎.
- **If you forget the file we tell you BEFORE Meta does:** `422 TEMPLATE_HEADER_MEDIA_REQUIRED`, with `details.example` = the exact component that's missing. Meta answers an opaque `132000` instead.
- And if it gets through anyway (a template we haven't synced yet), the message fails on the **first attempt** with `lastErrorCode` = `TEMPLATE_PARAM_MISMATCH` — we don't retry a payload that cannot work five times.
- **What the link needs:** **https**, pointing **straight at the file** (no login, no landing page), with the right `Content-Type`. ⚠ A Google Drive or Dropbox "share" link returns an **HTML page**: Meta downloads that and rejects the send. Meta's limits: document 100 MB, image 5 MB, video 16 MB.
- Meta **caches your link for 10 minutes**: if you change the file behind the same URL, add a different `?v=…`.
