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

# Send verification codes (OTP)

> Send one-time passwords over WhatsApp with authentication templates through the Vambe API.

## 🔐 What you'll build

You'll send a one-time verification code (OTP) to a customer over WhatsApp, using an **authentication template** and the Vambe API, and follow its delivery.

> [!TIP]
>
> Authentication templates are the WhatsApp format for login codes, account
> verification and password resets. WhatsApp renders the copy, adds a **Copy
> code** button and, for one-tap and zero-tap templates, can autofill the code
> in your Android app.

***

## Prerequisites

* ✅ [Connected WhatsApp Business API](/docs/connect-whatsapp-api)
* ✅ An **approved authentication template** on the number you send from
* ✅ Valid Vambe API key from the [developers page](https://app.vambeai.com/developers)

### Getting an authentication template

You can create it in either place:

* **In Vambe:** Templates → create a template with the **Authentication** category. Choose whether to add the security recommendation ("For your security, do not share this code.") and how many minutes the code is valid.
* **In WhatsApp Manager (Meta):** create it there and sync your templates in Vambe. Vambe imports it with its security recommendation, expiration and button type, ready to send.

Then copy its ID from `GET /api/public/templates`.

> [!WARNING]
>
> An authentication template only carries the code. WhatsApp writes the rest of
> the copy, so you can't add your own text, links or media to it.

***

## Choose an endpoint

| Endpoint | Use it when |
| - | - |
| `POST /api/public/whatsapp/message/send/{phone}/template/{templateId}` | You send one code and want the structured format: the phone in the path and the code as the first variable. |
| `POST /api/public/whatsapp/message/send/template/{templateId}` | You already call the smart endpoint for your other templates and want the same flow. |
| `POST /api/public/whatsapp/message/send/template/{templateId}/many` | You send codes to several recipients in one request. |

The code is never rewritten or extracted by AI. The structured endpoint sends `variables[0]` as is; the smart endpoints only trim spaces around it (`" 482913 "` is sent as `482913`), so store the trimmed value if you compare it later.

***

## Send a code

**Structured**

The phone goes in the path (country code, digits only) and the code is the only variable:

```bash theme={null}
curl -X POST \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "variables": ["482913"] }' \
  https://api.vambe.me/api/public/whatsapp/message/send/56912345678/template/{templateId}
```

**Response (201):**

```json theme={null}
{
  "messageId": "f1e2d3c4-b5a6-4978-8899-aabbccddeeff",
  "aiContactId": "b7c9a9e2-4a4b-4f4e-9d2a-1c2d3e4f5a6b",
  "ticketId": "0a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d",
  "contactPhoneNumber": "56912345678",
  "channelPhoneNumber": "56211111111",
  "variables": [ ... ]
}
```

**Smart**

Put the recipient and the code in the body:

```bash theme={null}
curl -X POST \
  -H "Content-Type: application/json" \
  -d '{ "phone": "+56 9 1234 5678", "code": "482913" }' \
  "https://api.vambe.me/api/public/whatsapp/message/send/template/{templateId}?x-api-key=YOUR_API_KEY"
```

The body keys can be named in any of these ways:

| Value | Accepted keys |
| - | - |
| Recipient phone | `phone`, `phone_number`, `phoneNumber`, `to` |
| Code | `code`, `otp`, `otp_code`, `otpCode` |

Spaces, `+`, parentheses and dashes in the phone are removed. The code can be a string or a number.

**Response (201):**

```json theme={null}
{
  "contactId": "b7c9a9e2-4a4b-4f4e-9d2a-1c2d3e4f5a6b",
  "messageId": "f1e2d3c4-b5a6-4978-8899-aabbccddeeff",
  "contactPhoneNumber": "56912345678",
  "channelPhoneNumber": "56211111111"
}
```

**Bulk**

Send an array; each item needs its own phone and code:

```bash theme={null}
curl -X POST \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '[
    { "phone": "56912345678", "code": "482913" },
    { "phone": "56987654321", "code": "190274" }
  ]' \
  https://api.vambe.me/api/public/whatsapp/message/send/template/{templateId}/many
```

Before anything is queued, every item is checked for a usable phone and code. If any item fails that check, the whole request is rejected with the position of each failing item and **nothing is queued**:

```json theme={null}
{
  "message": "Authentication templates need the recipient and the code in the body, e.g. {\"phone\": \"56912345678\", \"code\": \"123456\"}",
  "errors": [
    { "index": 1, "error": "Missing the OTP code. Authentication templates need the recipient and the code in the body, e.g. {\"phone\": \"56912345678\", \"code\": \"123456\"}" }
  ]
}
```

When every item passes, the response is `{ "success": true }` and each code is sent in the background, **independently**. A problem with one recipient found while sending (for example, a blocked contact or a number WhatsApp rejects) only affects that item, and the others may already have been sent. The response carries no message IDs.

### Optional parameters

* `from-phone-number` (query): the number to send from, when you have more than one, written exactly as it is registered in Vambe (digits with country code, e.g. `56211111111`).
  * **Structured endpoint:** if the number isn't one the template is approved on, the request fails with `Template cant be sent from this phone number`.
  * **Smart endpoints:** if it doesn't match any WhatsApp number of your account (mistyped, or formatted differently), the request fails with `from-phone-number … is not a WhatsApp number of this account`. If it's one of your numbers but the template isn't approved on it, it fails with `Template is not valid for this channel`. Either way nothing is sent from another number.
* `stage` (query, smart endpoints) or `stageId` (body, structured endpoint): the pipeline stage to put the contact in.

***

## Track delivery

For single sends (structured and smart), use the `messageId` from the response with [GET /api/public/whatsapp/message/status/{messageId}](/reference/whatsapp-message/get-the-delivery-status-of-a-sent-message). It starts as `accepted` and moves through `sent`, `delivered` and `read`, or ends in `failed` with WhatsApp's reason. A `404` right after sending means the message is still being registered: retry after a few seconds.

Bulk sends don't return message IDs. If you need to follow each code, send them one by one.

***

## Rules to keep in mind

> **Codes are up to 15 characters**
>
> That is WhatsApp's limit for authentication templates. Longer codes are rejected with `OTP code must be 15 characters or less`.

> **Repeating a request does not resend the code**
>
> The smart endpoints are idempotent: a request identical to a previous one within **10 minutes** counts as one send and returns the original response. "Identical" means the same template, the **whole body**, `stage` and `from-phone-number`:
>
> * Retrying the exact same request sends nothing new, so to resend a code to the same phone inside that window, generate a new code.
> * Any other difference, such as a timestamp or request ID in the body, a different `stage` or another `from-phone-number`, makes it a new request and **sends the code again**. If you rely on this protection against duplicate sends, retry with exactly the same body.
>
> The structured endpoint is not idempotent: every call sends a message.

> **The recipient needs a phone number**
>
> WhatsApp only delivers authentication templates to phone numbers. A contact Vambe knows only by its WhatsApp username (BSUID), without a phone, can't receive one.

> **Treat the code as a secret**
>
> Generate a new random code for every attempt, keep its expiration short (it should match the minutes set on the template) and never log it in plain text on your side.

***

## Error reference

| Status | Message | What to do |
| - | - | - |
| 400 | `OTP code is required for authentication templates` | Structured endpoint: send the code as the first variable, `{"variables": ["482913"]}`. |
| 400 | `Missing the OTP code. …` | Smart endpoints: add the code to the body (`code`, `otp`, `otp_code` or `otpCode`). |
| 400 | `Missing the recipient phone. …` | Smart endpoints: add the phone to the body (`phone`, `phone_number`, `phoneNumber` or `to`). |
| 400 | `Invalid recipient phone "…"` | Send digits with country code; spaces, `+`, parentheses and dashes are fine. |
| 400 | `OTP code must be 15 characters or less` | Shorten the code. |
| 400 | `Template cant be sent from this phone number` | Structured endpoint: use a `from-phone-number` the template is approved on, or omit it. |
| 400 | `from-phone-number … is not a WhatsApp number of this account` | Smart endpoints: write the number exactly as registered in Vambe (digits with country code), or omit it. |
| 400 | `Template is not valid for this channel` | Smart endpoints: use a `from-phone-number` the template is approved on, or omit it. |
| 400 | `Authentication templates cannot be sent to BSUID-only contacts. A phone number is required.` | Send to the customer's phone number. |
| 401 | `Unauthorized` | Check the API key. |
| 429 | `An identical send is already being processed. …` | An identical request is still in flight; retry shortly to get its result. |

***

## Next steps

> **[Send template messages](/docs/send-template-message)**
>
> Send your other templates with variables.

> **[Create templates](/docs/create-template)**
>
> Design and submit templates for approval.


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