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

# Send a WhatsApp message

> Send one WhatsApp message through a broadcast and track the recipient in Botcadence

Send one WhatsApp message to one recipient and record it under an existing broadcast. The request does not start the broadcast or send to its saved contact list. API recipients appear in that broadcast's recipient history with their delivery status.

## End-to-end setup

1. In Botcadence, connect a WhatsApp number to the project.
2. Create a WhatsApp broadcast and select its sender. Save the broadcast. It can stay in draft; this API call does not launch it.
3. For template sends, attach an approved WhatsApp template to the broadcast. The template's named variables are the keys you pass in `data`.
4. Create a project API key with the `whatsapp:create` scope. Keep the key on your server.
5. Open the broadcast in the dashboard and copy its ID from the URL. Copy the organization and project IDs into the request below as well.

The broadcast supplies the WhatsApp channel and sender. The API key must belong to the organization and project in the URL.

## Authentication

Send the project API key as a Bearer token. See [API authentication](/docs/api-reference/authentication).

```http theme={null}
Authorization: Bearer bot_live_YOUR_KEY
```

**Required scope:** `whatsapp:create`

## Path parameters

<ParamField path="organization_id" type="string" required>
  Organization that owns the project and API key.
</ParamField>

<ParamField path="project_id" type="string" required>
  Project containing the broadcast and connected WhatsApp number.
</ParamField>

<ParamField path="broadcast_id" type="string" required>
  Existing broadcast used to track this send. The broadcast's contact list is not sent.
</ParamField>

## Request body

Use one of two request shapes:

* **Broadcast template:** omit `type` and pass the recipient in `to`. Pass named template variables in `data`. You cannot choose another template in the request.
* **Typed message:** pass `type` and a matching `payload`. Do not include `data` in a typed message request.

<ParamField body="to" type="string" required>
  Recipient phone number in international format, such as `+14155552671`. Spaces, parentheses, periods, and hyphens are removed.
</ParamField>

<ParamField body="data" type="object">
  Named values for variables in the broadcast's attached template. The keys must match the template's variables exactly. Media headers use `header_media_url`; location headers use `header_location` with `latitude` and `longitude`. Omit this field when the template has no variables.
</ParamField>

<ParamField body="type" type="string">
  Typed message: `text`, `image`, `video`, `audio`, `voice`, `document`, `sticker`, `location`, `contacts`, `interactive`, `reaction`, `product`, `product_list`, or `catalog`.
</ParamField>

<ParamField body="payload" type="object">
  Fields for the selected message type. Media uses a publicly reachable HTTPS URL; this API does not upload files.
</ParamField>

## Example 1: send the broadcast's approved template

Suppose the approved template attached to the broadcast has the named variables `first_name` and `order_id`. Send those values in `data`; the API gets the template name, language, channel, and sender from the broadcast settings.

```bash theme={null}
curl -X POST \
  "https://backend.botcadence.com/organizations/ORG_ID/projects/PROJECT_ID/broadcasts/BROADCAST_ID/messages" \
  -H "Authorization: Bearer $BOTCADENCE_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1042-confirmation" \
  -d '{
    "to": "+14155552671",
    "data": {
      "first_name": "Alex",
      "order_id": "ORD-1042"
    }
  }'
```

If the attached template has no variables, send only `to`, or pass an empty `data` object. A template send can be used when the free-form messaging window is closed, provided the template is approved and the account is eligible to send it.

## Example 2: send a text message

Free-form messages such as text require an open WhatsApp messaging window for the recipient.

```bash theme={null}
curl -X POST \
  "https://backend.botcadence.com/organizations/ORG_ID/projects/PROJECT_ID/broadcasts/BROADCAST_ID/messages" \
  -H "Authorization: Bearer $BOTCADENCE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+14155552671",
    "type": "text",
    "payload": {
      "body": "Hi Alex, your order is ready.",
      "preview_url": true
    }
  }'
```

`preview_url` is optional and defaults to `true`.

## Example 3: send an image or document from a URL

The URL must use HTTPS and Meta must be able to fetch it without signing in. For a document, `filename` and `caption` are optional.

<CodeGroup>
  ```bash Image theme={null}
  curl -X POST \
    "https://backend.botcadence.com/organizations/ORG_ID/projects/PROJECT_ID/broadcasts/BROADCAST_ID/messages" \
    -H "Authorization: Bearer $BOTCADENCE_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "to": "+14155552671",
      "type": "image",
      "payload": {
        "link": "https://cdn.example.com/order-1042.png",
        "caption": "Your order is ready"
      }
    }'
  ```

  ```bash Document theme={null}
  curl -X POST \
    "https://backend.botcadence.com/organizations/ORG_ID/projects/PROJECT_ID/broadcasts/BROADCAST_ID/messages" \
    -H "Authorization: Bearer $BOTCADENCE_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "to": "+14155552671",
      "type": "document",
      "payload": {
        "url": "https://cdn.example.com/invoices/ORD-1042.pdf",
        "filename": "invoice-ORD-1042.pdf",
        "caption": "Your invoice"
      }
    }'
  ```
</CodeGroup>

Use either `link` or `url`; both are accepted. The API forwards the URL to WhatsApp and does not store a copy of the file.

## Other message types

All typed messages use the same endpoint. `payload` contains the fields for the selected type.

| `type`                      | Example `payload`                                                                                                                                                | Notes                                                          |
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
| `video`                     | `{"link":"https://cdn.example.com/clip.mp4","caption":"Watch this"}`                                                                                             | HTTPS URL; caption optional.                                   |
| `audio`                     | `{"link":"https://cdn.example.com/audio.mp3"}`                                                                                                                   | HTTPS URL.                                                     |
| `voice`                     | `{"link":"https://cdn.example.com/voice.ogg"}`                                                                                                                   | Sent as an audio voice note.                                   |
| `sticker`                   | `{"link":"https://cdn.example.com/sticker.webp"}`                                                                                                                | HTTPS URL.                                                     |
| `location`                  | `{"latitude":37.7898,"longitude":-122.3942,"name":"Ferry Building","address":"San Francisco"}`                                                                   | Latitude must be -90 to 90; longitude -180 to 180.             |
| `contacts`                  | `{"contacts":[{"name":{"formatted_name":"Ada Lovelace","first_name":"Ada","last_name":"Lovelace"},"phones":[{"phone":"+14155552671","type":"CELL"}]}]}`          | Provide a non-empty array in WhatsApp contact format.          |
| `interactive`               | `{"type":"button","body":{"text":"Confirm your order?"},"action":{"buttons":[{"type":"reply","reply":{"id":"confirm","title":"Confirm"}}]}}`                     | Supply a WhatsApp interactive object with `type` and `action`. |
| `reaction`                  | `{"message_id":"wamid.MESSAGE_TO_REACT_TO","emoji":"👍"}`                                                                                                        | `message_id` is the WhatsApp message being reacted to.         |
| `product`                   | `{"body":{"text":"View this item"},"action":{"catalog_id":"CATALOG_ID","product_retailer_id":"SKU-1042"}}`                                                       | Uses the WhatsApp catalog connected to the sender.             |
| `product_list` or `catalog` | `{"body":{"text":"Choose an item"},"action":{"catalog_id":"CATALOG_ID","sections":[{"title":"Featured","product_items":[{"product_retailer_id":"SKU-1042"}]}]}}` | Uses catalog IDs and product retailer IDs from Meta.           |

For example, send a location with:

```json theme={null}
{
  "to": "+14155552671",
  "type": "location",
  "payload": {
    "latitude": 37.7898,
    "longitude": -122.3942,
    "name": "Ferry Building",
    "address": "San Francisco"
  }
}
```

## Idempotent retries

You can send an `Idempotency-Key` header of 1–255 characters. If the original request included the header, retry a timeout with the same key and request body. For 24 hours, repeating the same key and request returns the saved result instead of sending again. Reusing a key with a different request returns `409 Conflict`. If the provider outcome is still being resolved, the API returns `202 Accepted` while it waits for a delivery status update. Without an idempotency key on the original request, a timeout retry may send a duplicate.

```http theme={null}
Idempotency-Key: order-1042-confirmation
```

## Response

When Meta accepts the request, the API returns the internal message ID, recipient, send status, and Meta's message ID when available. Acceptance does not mean the recipient has received or read the message; delivery updates arrive later and appear in the broadcast recipient history.

```json theme={null}
{
  "status": "success",
  "message": "WhatsApp message accepted by Meta",
  "data": {
    "message_id": "665f1a2b3c4d5e6f7a8b9c0d",
    "broadcast_id": "665f1a2b3c4d5e6f7a8b9c0e",
    "to": "+14155552671",
    "type": "template",
    "status": "sent",
    "provider_message_id": "wamid.HBgLMTQxNTU1NTI2NzEVAgARGBI..."
  },
  "status_code": 200
}
```

## Limits and errors

An active plan, available WhatsApp send quota, a connected sender, and messaging eligibility are required. A successful Meta acceptance consumes one WhatsApp send. If Meta definitely rejects the request, the API marks it failed and releases the reserved send. If the provider outcome is uncertain and the original request included `Idempotency-Key`, retry with the same key while the API resolves the status webhook. Otherwise, check the broadcast history before deciding whether to retry.

| Status | Meaning                                                                                                                     |
| ------ | --------------------------------------------------------------------------------------------------------------------------- |
| `400`  | Invalid request, unavailable sender, closed messaging window for a typed message, invalid template, or recipient opted out. |
| `401`  | API key is missing, invalid, or revoked.                                                                                    |
| `402`  | Plan is inactive or WhatsApp send quota is exhausted.                                                                       |
| `403`  | API key belongs to another organization/project or lacks `whatsapp:create`.                                                 |
| `404`  | Broadcast does not exist in the organization and project.                                                                   |
| `409`  | Idempotency key was already used with a different request.                                                                  |
| `202`  | A send using this idempotency key is processing or its provider outcome is still being resolved.                            |
| `503`  | Provider outcome is unknown. A repeated request with the same idempotency key returns `202` until the webhook resolves it.  |
