---
title: Send WhatsApp
description: POST /v1/whatsapp — send a transactional WhatsApp message using a pre-approved template.
---

import Endpoint from '../../../components/Endpoint.astro';

Send a transactional WhatsApp message to a phone number in
international format.

<Endpoint method="POST" path="/v1/whatsapp" />

## Request

```bash
curl https://api.zevsend.com/v1/whatsapp \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+2348012345678",
    "template_id": "tpl_01HXYZ...",
    "variables": {
      "first_name": "Ada",
      "order_id": "ORD-1234"
    }
  }'
```

### Body parameters

| Field             | Type   | Required  | Description                                                                                                  |
| ----------------- | ------ | --------- | ------------------------------------------------------------------------------------------------------------ |
| `to`              | string | yes       | Recipient phone in E.164.                                                                                     |
| `template_id`     | string | yes       | Template public id (`tpl_…`). Must be a `whatsapp` template.                                                  |
| `variables`       | object | sometimes | Values for the template's declared `customer.*` variables.                                                    |
| `brand_domain_id` | string | no        | Pin the send to a specific verified domain (`dom_…`) for brand variables like `{{brand.name}}`.                |

## Response

```http
HTTP/1.1 202 Accepted
Content-Type: application/json

{
  "id": "wam_01HXYZ...",
  "to": "+2348012345678",
  "status": "sending",
  "sandbox": false,
  "created_at": "2025-05-24T12:34:56.789Z"
}
```

## Lifecycle

WhatsApp has one more terminal state than email or SMS — a
`read` event fires when the recipient opens the conversation.

| State       | Meaning                                                              |
| ----------- | -------------------------------------------------------------------- |
| `queued`    | Persisted but not yet handed off.                                     |
| `sending`   | Handed off; awaiting accept.                                          |
| `sent`      | Accepted and in transit.                                              |
| `delivered` | Confirmed at the recipient's device.                                  |
| `read`      | Recipient opened the conversation.                                    |
| `failed`    | Rejected at submit or permanent delivery failure.                     |

The `read` state is best-effort. WhatsApp only fires read
receipts when the recipient has read-receipts enabled in their
own settings, so a message that's been read may never visibly
transition past `delivered`. Don't treat the absence of `read`
as a signal that the message wasn't seen.

## Templates only

Every WhatsApp send must reference a pre-approved template.
Raw text isn't accepted — the upstream network would reject
it anyway, so we reject it at the API.

The variable manifest on the template encodes the positional
order of placeholders in the approved template body. You pass
named variables; we map them to positions at send time. The
upstream network renders the message body from the approved
template using your variable values.

## Pricing

WhatsApp templates carry a per-send price, set on the template
in your team's billing currency. Templates can also have
per-country overrides that take precedence over the default rate
when sending to those countries. The price you see on the
template is what you pay. We never expose which upstream
provider handled the send.

See the [Pricing guide](/guide/pricing#whatsapp) for the full
charge cascade and how Zev Credit is debited.

## Errors

| Status | Code                       | Cause                                                            |
| ------ | -------------------------- | ---------------------------------------------------------------- |
| `400`  | `invalid_phone`            | `to` isn't a valid E.164 number.                                 |
| `400`  | `template_id_required`     | `template_id` was missing.                                       |
| `404`  | `template_not_found`       | No such template on this team.                                   |
| `409`  | `wrong_template_channel`   | The template is for email or SMS.                                |
| `422`  | `missing_variable`         | A required variable was missing.                                 |
| `422`  | `unknown_variable`         | You passed a variable the template doesn't declare.              |
| `403`  | `no_approved_brand_domain` | You're live but no approved domain exists for the team.          |
| `403`  | `recipient_not_allowed`    | Sandbox-only: recipient hasn't been verified on this team.       |
| `503`  | `no_carrier_available`     | No enabled WhatsApp provider is currently online.                |

## Conversation windows

WhatsApp enforces 24-hour conversation windows: once a recipient
last messaged you, you have 24 hours to send free-form messages
back. Outside that window only pre-approved templates of the
right category can be delivered. ZevSend is template-only, so
this is enforced by design.

Template categories (set when the template is registered):

| Category       | Used for                                                                |
| -------------- | ----------------------------------------------------------------------- |
| `AUTHENTICATION` | OTPs, login codes, verification messages.                              |
| `UTILITY`        | Account updates, order confirmations, shipping notifications.          |
| `MARKETING`      | Promotional content. Limited per recipient per day.                     |

Pick the right category when you author the template — picking
wrong is the most common reason templates get rejected.