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

# WhatsApp Template Delivery

> How Osvi asks your gateway to send an approved WhatsApp template, to start a conversation from a workflow campaign.

> [!NOTE]
>
> **Preview.** Workflow campaigns that message on WhatsApp are being rolled out to pilot accounts. This page is the contract your gateway implements for them; nothing changes for agents you already run.

A [workflow campaign](/platform/campaigns.md) can have a **chat step**: a chat agent messages a contact on WhatsApp, waits for the reply, and the conversation's outcome decides what happens next.

WhatsApp only lets a business message someone first with a **template** that Meta has approved, and only within 24 hours of the customer's last message can it send free text. Osvi doesn't hold your WhatsApp Business account; your gateway (or your WhatsApp provider) does. So for the first message of a chat step, Osvi asks your gateway to send a template, and your gateway sends it.

Everything after that first message works the way it does today:

* The customer's reply reaches Osvi through [Send Message](/api-reference/endpoint/chat/send_message.md) or [Send Message (Async)](/api-reference/endpoint/chat/inbound_async.md), as any inbound message does.
* The agent's replies come back to you on the [Chat Delivery Webhook](/api-reference/endpoint/chat/delivery_webhook.md).

## Where it's sent

Template requests go to the same place as every other delivery: the chat agent's **Delivery URL**, with the agent's **Delivery headers**. The agent's **Settings** tab has both, in the **Delivery** card. A chat agent needs a delivery URL before it can be added to a workflow campaign.

> [!WARNING]
>
> As with other deliveries, these requests are **not signed**. Check for a custom header you configured (for example a bearer token) to verify a request came from Osvi.

## Telling the two apart

Every template request carries `"kind": "template"`. Ordinary reply deliveries have no `kind` field. Treat a missing `kind` as an ordinary reply, so an endpoint that already handles replies keeps working unchanged.

## The template envelope

```json theme={null}
{
  "kind": "template",
  "session_id": 48213,
  "agent_uuid": "6f1c2a0e-6c55-4c1b-9a7e-2d1f3b9e8c11",
  "customer_id": "+919876543210",
  "template": {
    "name": "missed_call_v1",
    "language": "en",
    "params": {
      "1": "Priya",
      "2": "your renewal"
    }
  },
  "idempotency_key": "wf_5c0d8a54-2a7b-4f6e-9a35-1f0f6f9d2b11_step_3_attempt_1",
  "workflow": {
    "campaign_id": 3,
    "run_ref": "R-20418"
  },
  "ts": 1760000000
}
```

| Field | Description |
| - | - |
| `kind` | Always `template` for these requests. |
| `session_id` | The chat session Osvi opened for this conversation. The customer's replies belong to it. |
| `agent_uuid` | The chat agent sending the message. |
| `customer_id` | The contact's phone number in E.164 format (`+` and the country code). |
| `template.name` | The approved template's name, exactly as registered with Meta. |
| `template.language` | The template's language code, as registered with Meta (for example `en` or `en_US`). |
| `template.params` | Values for the template's placeholders, keyed by placeholder: `"1"`, `"2"`… for numbered placeholders, or the names for named ones. Always strings. |
| `idempotency_key` | Unique per send attempt of a step. The same key is used when Osvi retries the same request (see below). Use it to make sure the contact gets the message once. |
| `workflow.campaign_id` | The workflow campaign the message is for. |
| `workflow.run_ref` | The contact's run in that campaign, as shown in the campaign's **Logs**. |
| `ts` | Unix timestamp (seconds) when the request was made. |

## Registering templates

You choose which templates a workflow may send. Under the chat agent's **Settings**, in the **Delivery** card, list each approved template with:

* its **name** and **language**;
* its **placeholders**, in order, with a short description of each (for example "1: the contact's first name").

The workflow editor only offers templates listed there. In each chat step, the operator maps every placeholder to a contact column, an answer from an earlier step, or fixed text.

## Your response

Answer within **15 seconds**.

| Response | Meaning | What Osvi does |
| - | - | - |
| **2xx** | The template was accepted for sending. | The step waits for the contact's reply. |
| **400, 404, 409 or 422** | Rejected for good. Put the reason in the body (see below). | The step ends as **not delivered** with your reason, shown in plain words in the campaign's Logs. Osvi does not retry. |
| **429, any 5xx, a timeout or a connection error** | Try again later. | Retried, then the step ends as **not delivered** (see below). |

A 2xx body is optional. If you send one, Osvi keeps the provider's message id with the step:

```json theme={null}
{ "status": "sent", "provider_message_id": "wamid.HBgMOTE5ODc2NTQzMjEwFQIAERgS" }
```

A rejection body names the reason:

```json theme={null}
{ "reason": "template_not_approved", "message": "missed_call_v1 is paused by Meta" }
```

Use one of these reasons where it fits, or your own short code:

| `reason` | When |
| - | - |
| `template_not_approved` | The template doesn't exist, isn't approved, or is paused. |
| `invalid_number` | The number isn't on WhatsApp or can't be messaged. |
| `opted_out` | The contact has opted out of messages from you. |
| `params_invalid` | The placeholders don't match the template. |
| `quality_limited` | Meta has limited the sending number (quality rating or messaging tier). |

## Retries

Retries follow the same schedule as reply deliveries: up to **6 attempts in total**, a few seconds apart at first and up to about a minute apart later. A `Retry-After` header on a 429 is respected.

Every attempt carries the same `idempotency_key`. If you already sent the template for that key, answer 2xx again and don't send it a second time.

## Throughput

When a campaign's batch starts, Osvi sends template requests at a steady rate per agent, not all at once. If your provider limits your sending rate, answer **429** with a `Retry-After` header and Osvi slows down.

## What a test request looks like

When the operator sends a test message from the workflow editor, Osvi sends the same envelope, with the operator's own number as `customer_id`. Treat it like any other request.

## Checklist for your gateway

1. Route envelopes by `kind`: `template` sends a template, and no `kind` sends the reply text as you do today.
2. Send the template with your WhatsApp provider, using `template.name`, `template.language` and `template.params`.
3. De-duplicate on `idempotency_key`.
4. Answer 2xx when the provider accepts the message, 4xx with a `reason` when it refuses for good, and 429 or 5xx when it's worth trying again.
5. Forward the customer's replies to Osvi for the same `customer_id` and agent, as you already do.
6. Give us a stage (sandbox) delivery URL to test against before your production one.


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