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

# Send a template message

> Send a Meta-approved WhatsApp template with variable substitution.

Send an approved WhatsApp template to a phone number. Template messages are the only way to initiate a WhatsApp conversation outside the 24-hour session window.

## Endpoint

```
POST /api/v1/{subdomain}/messages/template
```

Required ability: `messages.send`.

## Headers

| Header          | Value                                       |
| --------------- | ------------------------------------------- |
| `Authorization` | `Bearer <your-64-hex-token>`                |
| `Content-Type`  | `application/json` or `multipart/form-data` |
| `Accept`        | `application/json`                          |

<Tip>Use `multipart/form-data` if you upload a header image, video, or document directly (`header_image_file`, `header_video_file`, `header_document_file`).</Tip>

## Path parameters

<ParamField path="subdomain" type="string" required={true}>
  Your workspace subdomain.
</ParamField>

## Body parameters

<ParamField body="phone_number" type="string" required={true}>
  Recipient phone number. 8–15 characters.
</ParamField>

<ParamField body="template_name" type="string" required={true}>
  Name of the template (as registered with Meta).
</ParamField>

<ParamField body="template_language" type="string" required={true}>
  Template language code. 2–10 characters (e.g. `en_US`).
</ParamField>

<ParamField body="from_phone_number_id" type="string" required={false}>
  Meta phone-number id to send from. Defaults to the workspace's primary number.
</ParamField>

### Header media (pick one pair)

<ParamField body="header_image_url" type="string" required={false}>
  URL of a hosted image. Used when the template header type is `IMAGE`.
</ParamField>

<ParamField body="header_image_file" type="file" required={false}>
  Upload an image. Max 5120 KB.
</ParamField>

<ParamField body="header_video_url" type="string" required={false}>
  URL of a hosted video.
</ParamField>

<ParamField body="header_video_file" type="file" required={false}>
  Upload a video. Max 16384 KB.
</ParamField>

<ParamField body="header_document_url" type="string" required={false}>
  URL of a hosted document.
</ParamField>

<ParamField body="header_document_file" type="file" required={false}>
  Upload a document. Max 102400 KB.
</ParamField>

<ParamField body="header_document_name" type="string" required={false}>
  Display filename for the header document. Max 255 characters.
</ParamField>

### Variable substitution

<ParamField body="header_field_1" type="string" required={false}>
  Value for `{{1}}` in a text header.
</ParamField>

<ParamField body="field_1" type="string" required={false}>
  Value for `{{1}}` in the body.
</ParamField>

<ParamField body="field_2" type="string" required={false}>Value for `{{2}}` in the body.</ParamField>
<ParamField body="field_3" type="string" required={false}>Value for `{{3}}` in the body.</ParamField>
<ParamField body="field_4" type="string" required={false}>Value for `{{4}}` in the body.</ParamField>
<ParamField body="field_5" type="string" required={false}>Value for `{{5}}` in the body.</ParamField>
<ParamField body="field_6" type="string" required={false}>Value for `{{6}}` in the body.</ParamField>
<ParamField body="field_7" type="string" required={false}>Value for `{{7}}` in the body.</ParamField>
<ParamField body="field_8" type="string" required={false}>Value for `{{8}}` in the body.</ParamField>
<ParamField body="field_9" type="string" required={false}>Value for `{{9}}` in the body.</ParamField>
<ParamField body="field_10" type="string" required={false}>Value for `{{10}}` in the body.</ParamField>

<ParamField body="button_0" type="string" required={false}>
  Button 0 dynamic parameter.
</ParamField>

<ParamField body="button_1" type="string" required={false}>
  Button 1 dynamic parameter.
</ParamField>

<ParamField body="button_2" type="string" required={false}>
  Button 2 dynamic parameter.
</ParamField>

<ParamField body="copy_code" type="string" required={false}>
  One-time password for OTP templates. Max 100 characters.
</ParamField>

<ParamField body="contact" type="object" required={false}>
  Optional contact payload for creating or updating the contact record behind this send.
</ParamField>

## Example request

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://dash.xobito.com/api/v1/acme/messages/template \
    -H "Authorization: Bearer <your_token>" \
    -H "Content-Type: application/json" \
    -H "Accept: application/json" \
    -d '{
      "phone_number": "+14155551234",
      "template_name": "order_confirmation",
      "template_language": "en_US",
      "field_1": "John",
      "field_2": "#A1027"
    }'
  ```

  ```javascript JavaScript theme={null}
  const res = await fetch(
    "https://dash.xobito.com/api/v1/acme/messages/template",
    {
      method: "POST",
      headers: {
        Authorization: "Bearer <your_token>",
        "Content-Type": "application/json",
        Accept: "application/json",
      },
      body: JSON.stringify({
        phone_number: "+14155551234",
        template_name: "order_confirmation",
        template_language: "en_US",
        field_1: "John",
        field_2: "#A1027",
      }),
    }
  );
  const body = await res.json();
  ```

  ```python Python theme={null}
  import requests

  r = requests.post(
      "https://dash.xobito.com/api/v1/acme/messages/template",
      headers={
          "Authorization": "Bearer <your_token>",
          "Accept": "application/json",
      },
      json={
          "phone_number": "+14155551234",
          "template_name": "order_confirmation",
          "template_language": "en_US",
          "field_1": "John",
          "field_2": "#A1027",
      },
  )
  body = r.json()
  ```

  ```php PHP theme={null}
  <?php
  $ch = curl_init('https://dash.xobito.com/api/v1/acme/messages/template');
  curl_setopt_array($ch, [
      CURLOPT_POST => true,
      CURLOPT_RETURNTRANSFER => true,
      CURLOPT_HTTPHEADER => [
          'Authorization: Bearer <your_token>',
          'Content-Type: application/json',
          'Accept: application/json',
      ],
      CURLOPT_POSTFIELDS => json_encode([
          'phone_number'      => '+14155551234',
          'template_name'     => 'order_confirmation',
          'template_language' => 'en_US',
          'field_1'           => 'John',
          'field_2'           => '#A1027',
      ]),
  ]);
  $body = json_decode(curl_exec($ch), true);
  curl_close($ch);
  ```
</CodeGroup>

## Example response

```json 200 OK theme={null}
{
  "status": "success",
  "message": "Template message sent successfully",
  "data": {
    "message_id": "wamid.HBgLMTQxNTU1NTEyMzQVAgARGBI5QUQ1N...",
    "contact_id": 451,
    "phone": "+14155551234",
    "status": "sent",
    "sent_at": "2026-04-16T12:00:00.000000Z",
    "chat_id": 88,
    "contact_created": false
  }
}
```

## Error responses

| Status | When                                      | Example body                                                                                                |
| ------ | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| `401`  | Missing / invalid token                   | `{"status":"error","message":"Invalid API token"}`                                                          |
| `403`  | Missing ability                           | `{"status":"error","message":"Token does not have the required ability: messages.send"}`                    |
| `404`  | Template does not exist in your workspace | `{"status":"error","message":"Template not found"}`                                                         |
| `422`  | Validation, or template is not `APPROVED` | `{"status":"error","message":"Validation failed","errors":{"template_name":["Template is not APPROVED."]}}` |
| `429`  | Rate limit                                | `{"message":"Too many requests","retry_after":45}`                                                          |
| `500`  | Send failure                              | `{"status":"error","message":"Failed to send template message"}`                                            |

<Warning>
  Xobito enforces Meta's approval rule strictly: only templates with `status: "APPROVED"` can be sent. Use [`GET /templates`](/api-reference/endpoints/list-templates) to check status before sending.
</Warning>
