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

# Templates

> Pre-approved messages for WhatsApp Business outside the 24-hour window

Templates are pre-approved message formats required for contacting users outside the 24-hour conversation window. They must be submitted to Meta for approval before use.

## Why Templates?

* **Required by WhatsApp** - Only way to message users outside 24-hour window
* **Consistency** - Ensure uniform messaging across your team
* **Compliance** - Meta reviews content for quality and policy compliance
* **Analytics** - Track performance by template

## Quick Start: Send a Template Message

If you already have an approved template, here's how to send it with variables:

<CodeGroup>
  ```typescript TypeScript theme={null}
  const message = await zavu.messages.send({
    to: "+56912345678",
    messageType: "template",
    content: {
      templateId: "tpl_abc123",
      templateVariables: {
        "1": "John",
        "2": "ORD-12345",
        "3": "January 20, 2025"
      }
    }
  });
  ```

  ```python Python theme={null}
  message = zavu.messages.send(
      to="+56912345678",
      message_type="template",
      content={
          "template_id": "tpl_abc123",
          "template_variables": {
              "1": "John",
              "2": "ORD-12345",
              "3": "January 20, 2025"
          }
      }
  )
  ```

  ```ruby Ruby theme={null}
  message = client.messages.send(
    to: "+56912345678",
    message_type: "template",
    content: {
      template_id: "tpl_abc123",
      template_variables: {
        "1" => "John",
        "2" => "ORD-12345",
        "3" => "January 20, 2025"
      }
    }
  )
  ```

  ```go Go theme={null}
  message, err := client.Messages.Send(context.TODO(), zavudev.MessageSendParams{
      To:          zavudev.String("+56912345678"),
      MessageType: zavudev.String("template"),
      Content: &zavudev.MessageContentParams{
          TemplateID: zavudev.String("tpl_abc123"),
          TemplateVariables: map[string]string{
              "1": "John",
              "2": "ORD-12345",
              "3": "January 20, 2025",
          },
      },
  })
  ```

  ```php PHP theme={null}
  $message = $client->messages->send([
      'to' => '+56912345678',
      'messageType' => 'template',
      'content' => [
          'templateId' => 'tpl_abc123',
          'templateVariables' => [
              '1' => 'John',
              '2' => 'ORD-12345',
              '3' => 'January 20, 2025',
          ],
      ],
  ]);
  ```

  ```bash cURL theme={null}
  curl -X POST https://api.zavu.dev/v1/messages \
    -H "Authorization: Bearer zv_live_xxx" \
    -H "Content-Type: application/json" \
    -d '{
      "to": "+56912345678",
      "messageType": "template",
      "content": {
        "templateId": "tpl_abc123",
        "templateVariables": {
          "1": "John",
          "2": "ORD-12345",
          "3": "January 20, 2025"
        }
      }
    }'
  ```
</CodeGroup>

### How Variables Work

Template variables use **positional numbering**: `{{1}}`, `{{2}}`, `{{3}}`, etc. When sending, pass the values as an object where keys are the position numbers:

```
Template body:  "Hi {{1}}, your order #{{2}} is confirmed. Delivery: {{3}}."
                     ^              ^                              ^
                     |              |                              |
Variables:      "1": "John"   "2": "ORD-12345"          "3": "January 20, 2025"
```

<Warning>
  You must provide values for **all** variables defined in the template. Missing variables will cause the message to fail.
</Warning>

## Full Workflow

Templates follow a 3-step process: **Create** -> **Submit for Approval** -> **Send**.

### Step 1: Create a Template

<CodeGroup>
  ```typescript TypeScript theme={null}
  const template = await zavu.templates.create({
    name: "order_confirmation",
    language: "en",
    body: "Hi {{1}}, your order #{{2}} has been confirmed! Estimated delivery: {{3}}.",
    whatsappCategory: "UTILITY",
    variables: ["customer_name", "order_id", "delivery_date"]
  });

  console.log(template.id);     // "tpl_abc123"
  console.log(template.status); // "draft"
  ```

  ```python Python theme={null}
  template = zavu.templates.create(
      name="order_confirmation",
      language="en",
      body="Hi {{1}}, your order #{{2}} has been confirmed! Estimated delivery: {{3}}.",
      whatsapp_category="UTILITY",
      variables=["customer_name", "order_id", "delivery_date"]
  )

  print(template.id)     # "tpl_abc123"
  print(template.status) # "draft"
  ```

  ```bash cURL theme={null}
  curl -X POST https://api.zavu.dev/v1/templates \
    -H "Authorization: Bearer zv_live_xxx" \
    -H "Content-Type: application/json" \
    -d '{
      "name": "order_confirmation",
      "language": "en",
      "body": "Hi {{1}}, your order #{{2}} has been confirmed! Estimated delivery: {{3}}.",
      "whatsappCategory": "UTILITY",
      "variables": ["customer_name", "order_id", "delivery_date"]
    }'
  ```
</CodeGroup>

The `variables` array is optional and purely for documentation - it helps you remember what each position represents.

### Step 2: Submit for Approval

Templates start in `draft` status. You need to explicitly submit them to Meta for review:

<CodeGroup>
  ```typescript TypeScript theme={null}
  const submitted = await zavu.templates.submit("tpl_abc123", {
    senderId: "sender_12345",
    category: "UTILITY"
  });

  console.log(submitted.status); // "pending"
  ```

  ```python Python theme={null}
  submitted = zavu.templates.submit(
      "tpl_abc123",
      sender_id="sender_12345",
      category="UTILITY"
  )

  print(submitted.status)  # "pending"
  ```

  ```bash cURL theme={null}
  curl -X POST https://api.zavu.dev/v1/templates/tpl_abc123/submit \
    -H "Authorization: Bearer zv_live_xxx" \
    -H "Content-Type: application/json" \
    -d '{
      "senderId": "sender_12345",
      "category": "UTILITY"
    }'
  ```
</CodeGroup>

<Note>
  The `senderId` must be a sender with a WhatsApp Business Account connected. The template is submitted to Meta through that WABA.
</Note>

### Step 3: Check Approval Status

```bash theme={null}
curl https://api.zavu.dev/v1/templates/tpl_abc123 \
  -H "Authorization: Bearer zv_live_xxx"
```

```json theme={null}
{
  "id": "tpl_abc123",
  "name": "order_confirmation",
  "status": "approved",
  "category": "UTILITY",
  "body": "Hi {{1}}, your order #{{2}} has been confirmed! Estimated delivery: {{3}}.",
  "variables": ["customer_name", "order_id", "delivery_date"]
}
```

| Status     | Description                           |
| ---------- | ------------------------------------- |
| `draft`    | Created but not yet submitted to Meta |
| `pending`  | Submitted, awaiting Meta review       |
| `approved` | Ready to use                          |
| `rejected` | Not approved - check rejection reason |

<Warning>
  Only `approved` templates can be used to send messages. Attempting to use draft, pending, or rejected templates will fail.
</Warning>

### Step 4: Send the Template

Once approved, send it using the positional variables (see [Quick Start](#quick-start-send-a-template-message) above).

## Template Categories

| Category         | Use Case                                | Approval Speed |
| ---------------- | --------------------------------------- | -------------- |
| `UTILITY`        | Order updates, shipping, account alerts | Fast (hours)   |
| `MARKETING`      | Promotions, offers, newsletters         | Slower (days)  |
| `AUTHENTICATION` | OTPs, verification codes                | Fast (hours)   |

<Note>
  Choose the category that best matches your use case. Miscategorization can lead to rejection.
</Note>

## Templates with Buttons

Add call-to-action or quick reply buttons:

```bash theme={null}
curl -X POST https://api.zavu.dev/v1/templates \
  -H "Authorization: Bearer zv_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "order_delivered",
    "language": "en",
    "body": "Hi {{1}}, your order has been delivered!",
    "whatsappCategory": "UTILITY",
    "variables": ["customer_name"],
    "buttons": [
      {
        "type": "url",
        "text": "Track Order",
        "url": "https://example.com/track/{{1}}"
      },
      {
        "type": "quick_reply",
        "text": "Rate Delivery"
      }
    ]
  }'
```

### Button Types

| Type          | Description                                     | Max       |
| ------------- | ----------------------------------------------- | --------- |
| `quick_reply` | User response button                            | 3 buttons |
| `url`         | Opens a URL (can include variables)             | -         |
| `phone`       | Initiates a phone call                          | -         |
| `otp`         | Copy code / one-tap autofill for authentication | -         |

### Sending Templates with Dynamic URL Buttons

When a template has a URL button with a `{{1}}` placeholder, pass the substitution under `templateButtonVariables` keyed by the **button's position** in the template's `buttons` array.

`templateVariables` is for body placeholders. `templateButtonVariables` is for buttons. They use **different keys**:

* `templateVariables` keys → position of the placeholder in the body text (`"1"`, `"2"`, ...).
* `templateButtonVariables` keys → index of the button in the `buttons` array (`"0"`, `"1"`, `"2"`).

<Warning>
  **WhatsApp URL buttons only accept `{{1}}` (positional, numeric, no whitespace, no name).** Even though body placeholders may use named parameters like `{{name}}`, URL buttons do not. Anything else (`{{token}}`, `{{ 1 }}`, `{{user.id}}`, etc.) is approved by Meta as literal text in the URL — there is no way to substitute it later.

  If your template was approved with a non-`{{1}}` placeholder in a URL button, you must recreate the template with `{{1}}` and resubmit it for approval. Zavu now returns `400 invalid_request` instead of silently delivering broken URLs like `https://...%7B%7Bvariable%7D%7D`.
</Warning>

Example. Suppose your approved template looks like this:

```json theme={null}
{
  "id": "tpl_discipline_report",
  "body": "Hi {{1}}, your child received a {{2}}.",
  "buttons": [
    {
      "type": "url",
      "text": "View report",
      "url": "https://reporte.link/{{1}}"
    }
  ]
}
```

Send it like this:

<CodeGroup>
  ```typescript TypeScript theme={null}
  await zavu.messages.send({
    to: "+56912345678",
    messageType: "template",
    content: {
      templateId: "tpl_discipline_report",
      templateVariables: {
        "1": "Marco Ordaz",
        "2": "report for inappropriate behavior"
      },
      templateButtonVariables: {
        "0": "abc-report-token"
      }
    }
  });
  ```

  ```python Python theme={null}
  zavu.messages.send(
      to="+56912345678",
      message_type="template",
      content={
          "template_id": "tpl_discipline_report",
          "template_variables": {
              "1": "Marco Ordaz",
              "2": "report for inappropriate behavior",
          },
          "template_button_variables": {
              "0": "abc-report-token",
          },
      },
  )
  ```

  ```bash cURL theme={null}
  curl -X POST https://api.zavu.dev/v1/messages \
    -H "Authorization: Bearer zv_live_xxx" \
    -H "Content-Type: application/json" \
    -d '{
      "to": "+56912345678",
      "messageType": "template",
      "content": {
        "templateId": "tpl_discipline_report",
        "templateVariables": {
          "1": "Marco Ordaz",
          "2": "report for inappropriate behavior"
        },
        "templateButtonVariables": {
          "0": "abc-report-token"
        }
      }
    }'
  ```
</CodeGroup>

The recipient receives a button that opens `https://reporte.link/abc-report-token`.

#### Multiple buttons

For templates with several buttons, supply one entry per dynamic button. Static URL buttons (no `{{1}}`) and `quick_reply` buttons are not included in `templateButtonVariables`.

```json theme={null}
{
  "buttons": [
    { "type": "url", "text": "View order", "url": "https://shop.example.com/orders/{{1}}" },
    { "type": "quick_reply", "text": "Track" },
    { "type": "url", "text": "Help", "url": "https://help.example.com/" }
  ]
}
```

```json theme={null}
{
  "templateButtonVariables": {
    "0": "ORD-12345"
  }
}
```

Index `1` (quick reply) and index `2` (static URL) are skipped.

#### Validation rules

| Rule                                                          | Behavior                                                                                                                          |
| ------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| URL has `{{1}}` and `templateButtonVariables[index]` provided | ✅ delivered with substitution                                                                                                     |
| URL has `{{1}}` and `templateButtonVariables[index]` missing  | ❌ `400 invalid_request: Missing URL button parameter at index N`                                                                  |
| URL has `{{name}}`, `{{ 1 }}`, `{{token}}`, etc.              | ❌ `400 invalid_request: ... WhatsApp treats it as literal text. Recreate the template with "{{1}}" and resubmit it for approval.` |
| URL has more than one placeholder (e.g. `/{{1}}/{{2}}`)       | ❌ `400 invalid_request: at most one positional variable`                                                                          |
| URL has no placeholder (static link)                          | ✅ ignored — do not include the index in `templateButtonVariables`                                                                 |

## Authentication Templates (OTP)

For verification codes, use `AUTHENTICATION` category with OTP buttons:

```bash theme={null}
curl -X POST https://api.zavu.dev/v1/templates \
  -H "Authorization: Bearer zv_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "login_otp",
    "language": "en",
    "body": "{{1}} is your verification code.",
    "whatsappCategory": "AUTHENTICATION",
    "variables": ["otp_code"],
    "buttons": [
      {
        "type": "otp",
        "text": "Copy Code",
        "otpType": "COPY_CODE"
      }
    ],
    "addSecurityRecommendation": true,
    "codeExpirationMinutes": 5
  }'
```

Send the OTP:

```bash theme={null}
curl -X POST https://api.zavu.dev/v1/messages \
  -H "Authorization: Bearer zv_live_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+56912345678",
    "messageType": "template",
    "content": {
      "templateId": "tpl_otp123",
      "templateVariables": {
        "1": "482910"
      }
    }
  }'
```

See the [OTP Templates guide](/guides/whatsapp/templates/otp) for more details.

## Managing Templates

### List Templates

```bash theme={null}
curl https://api.zavu.dev/v1/templates \
  -H "Authorization: Bearer zv_live_xxx"
```

### Delete Template

```bash theme={null}
curl -X DELETE https://api.zavu.dev/v1/templates/tpl_abc123 \
  -H "Authorization: Bearer zv_live_xxx"
```

## Best Practices

### Naming Conventions

Use descriptive, lowercase names with underscores:

```
order_confirmation
shipping_update
password_reset
appointment_reminder
promotional_offer
```

### Content Guidelines

**Do:**

* Use clear, concise language
* Include dynamic variables for personalization
* Provide value to the recipient
* Test with real data before production use

**Don't:**

* Use placeholder text like "\[insert name]"
* Include excessive capitalization or punctuation
* Send promotional content without consent
* Use misleading or clickbait content

## Common Rejection Reasons

| Reason                 | Solution                                      |
| ---------------------- | --------------------------------------------- |
| Variable syntax error  | Use `{{1}}`, `{{2}}` positional format        |
| Category mismatch      | Choose correct category for content           |
| Promotional in UTILITY | Use `MARKETING` category for promotions       |
| Missing opt-out        | Include unsubscribe option for marketing      |
| Policy violation       | Review Meta's commerce and messaging policies |

## Testing Templates

Test with your own phone number before sending to customers:

```bash theme={null}
curl -X POST https://api.zavu.dev/v1/messages \
  -H "Authorization: Bearer zv_test_xxx" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+your_phone_number",
    "messageType": "template",
    "content": {
      "templateId": "tpl_abc123",
      "templateVariables": {
        "1": "Test User",
        "2": "TEST-001",
        "3": "Tomorrow"
      }
    }
  }'
```

<Tip>
  Use test mode API keys (`zv_test_xxx`) during development to avoid charges.
</Tip>
