> ## 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.

# Sending messages

> Send a template, read its status, and receive status webhooks.

## Discover the template

Templates are listed as Meta holds them, with the placeholders each one expects, so your code can discover what to send without opening WhatsApp Manager. The list is cached for five minutes.

```bash theme={null}
curl https://tawked.com/v1/whatsapp/templates \
  -H "Authorization: Bearer tk_live_xxxxxxxxxxxxxxxxxxxx"
```

```json theme={null}
{
  "data": [
    {
      "name": "order_received_v1",
      "language": "ar",
      "category": "UTILITY",
      "status": "APPROVED",
      "placeholders": {
        "kind": "named",
        "header": [],
        "body": ["name", "order"],
        "buttons": [{ "index": 0, "type": "url" }]
      }
    }
  ]
}
```

## Send

Every placeholder the template declares is required, extra keys are refused, and values are strings of 1 to 1024 characters without newlines or control characters. Give `lang` when the template exists in more than one language.

```bash theme={null}
curl -X POST https://tawked.com/v1/whatsapp/messages \
  -H "Authorization: Bearer tk_live_xxxxxxxxxxxxxxxxxxxx" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1042-received" \
  -d '{
    "to": "0550000000",
    "template": "order_received_v1",
    "lang": "ar",
    "params": { "name": "أحمد", "order": "1042" },
    "buttons": [{ "index": 0, "parameter": "1042" }],
    "reference": "order-1042"
  }'
```

The answer is `202 Accepted`. Meta has taken the message; the status arrives afterwards.

```json theme={null}
{
  "id": "0f4c9c0e-6d2b-4b8a-9c3e-7a1d2e3f4a5b",
  "status": "accepted",
  "to": "+966550000000",
  "template": "order_received_v1",
  "lang": "ar",
  "reference": "order-1042",
  "price_halalas": 0,
  "created_at": "2026-09-06T12:00:00+03:00"
}
```

| Field       | Notes                                                                                        |
| ----------- | -------------------------------------------------------------------------------------------- |
| `to`        | A Saudi mobile in any [accepted format](/destinations), stored as E.164.                     |
| `template`  | The name exactly as WhatsApp Manager lists it.                                               |
| `lang`      | Meta's language code (`ar`, `en`, `en_US`). Required when the template has several.          |
| `params`    | Placeholder values, keyed by name for a named template or `"1"`, `"2"` for a positional one. |
| `header`    | `{ "text": "..." }` when the template has a text header placeholder; refused otherwise.      |
| `buttons`   | `[{ "index": 0, "parameter": "..." }]`, one entry per URL button with a placeholder.         |
| `reference` | Your own reference, 1 to 64 printable characters, echoed back and searchable in the console. |

<Tip>
  Send an `Idempotency-Key` per business event and retry a `502` or a network error with the same key. A replay returns the original `202` with `Idempotent-Replayed: true` and sends nothing. See [Reliability](/reliability#idempotency).
</Tip>

## Statuses and webhooks

`accepted` then `sent`, `delivered`, `read`, or `failed` from any of the first three. Statuses arrive from Meta within about a minute, are applied in order and never go backwards. A message stops being tracked 48 hours after the send and keeps its last status.

Read the current state with `GET /v1/whatsapp/messages/{id}`:

```json theme={null}
{
  "id": "0f4c9c0e-6d2b-4b8a-9c3e-7a1d2e3f4a5b",
  "status": "failed",
  "to": "+966550000000",
  "template": "order_received_v1",
  "lang": "ar",
  "reference": "order-1042",
  "price_halalas": 0,
  "error": { "code": "131026", "message": "Message undeliverable" },
  "created_at": "2026-09-06T12:00:00+03:00",
  "sent_at": null,
  "delivered_at": null,
  "read_at": null,
  "failed_at": "2026-09-06T12:00:03+03:00"
}
```

Or receive one webhook per status change, at the application's webhook URL, signed and retried exactly like `verification.*` events. Only the status reached is announced: a message that jumps straight to `read` gets one `message.read`. `to` is masked in webhooks the way the console shows it, so keep your own copy of the destination next to the returned `id` or your `reference`.

```json theme={null}
{
  "event": "message.delivered",
  "data": {
    "id": "0f4c9c0e-6d2b-4b8a-9c3e-7a1d2e3f4a5b",
    "status": "delivered",
    "application_id": "c2a10f3e-8b7a-4d2e-9c1a-1a2b3c4d5e6f",
    "to": "+9665•• ••• 000",
    "template": "order_received_v1",
    "lang": "ar",
    "reference": "order-1042",
    "price_halalas": 0,
    "error": null,
    "occurred_at": "2026-09-06T09:00:04.512Z",
    "created_at": "2026-09-06T09:00:01.087Z"
  },
  "sent_at": "1788685205201"
}
```

Signature verification is the same as for verification events: see [Webhooks](/webhooks#verifying-the-signature).
