> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.agentphone.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.agentphone.ai/_mcp/server.

# WhatsApp

> Send and receive WhatsApp messages — templates, quote replies, buttons, list menus, link buttons, and reactions

WhatsApp runs through the same `POST /v1/messages` endpoint as everything else. Connect your WhatsApp Business Account once, then send text, media, and WhatsApp's interactive message types from your numbers.

> **Note**
>
> **WhatsApp is in beta** and enabled on every account. Send feedback to [founders@agentphone.to](mailto:founders@agentphone.to).

## In this guide

* **Connecting**: link your WhatsApp Business Account
* **The 24-hour window**: the one rule that shapes everything else
* **Sending**: text, media, quote replies, buttons, list menus, link buttons
* **Templates**: the only way to message someone outside the window
* **Receiving**: inbound messages, button taps, and what they link back to
* **Billing**: Meta charges your WABA directly, not your AgentPhone credits

## Connecting

Go to **WhatsApp** in the dashboard and click **Connect WhatsApp**. That runs Meta's Embedded Signup in a popup, where you either create a WhatsApp Business Account or pick an existing one, then choose a phone number.

You need a phone number that is not already registered to WhatsApp. If the number you want is on WhatsApp Business today, delete it from that account first, or Meta will refuse it.

Once connected, `GET /integrations/whatsapp/status` returns your connection and the numbers on it:

```bash
curl https://api.agentphone.ai/integrations/whatsapp/status \
  -H "Authorization: Bearer $AGENTPHONE_API_KEY"
```

```json
{
  "connected": true,
  "enabled": true,
  "connections": [
    {
      "connection_id": "cms8btnuw0fsh7cfukmwr9bjh",
      "waba_id": "1372974988300999",
      "connected_at": "2026-07-31T02:31:44Z",
      "numbers": [
        { "id": "cmrjvwmf20099bey8uarx1n69", "phone_number": "+14788348706", "agent_id": "cmr460xqa01sybgnoppdf3y1f" }
      ]
    }
  ]
}
```

## The 24-hour window

This is the rule that shapes every WhatsApp integration, and it comes from Meta, not from us.

You can only send a free-form message to someone if they messaged you within the last 24 hours. That window opens on each inbound message and closes 24 hours after the most recent one. Inside it, send whatever you like. Outside it, Meta rejects the send and we return a 422.

The only way to reach someone outside the window is an approved [template](#templates). There is no workaround, and buying more numbers does not help.

To know where you stand, `GET /v1/conversations/{id}` reports it:

```json
{
  "capabilities": {
    "canSendMessages": true,
    "whatsappWindowExpiresAt": "2026-08-21T01:21:59Z"
  }
}
```

When the window has closed, `canSendMessages` is `false` and `sendDisabledReason` says so, so you can branch to a template instead of eating a failed send.

## Sending

Every send is `POST /v1/messages` with `channel: "whatsapp"`.

> **Note**
>
> `channel: "whatsapp"` is only required on numbers that carry both a WhatsApp connection and a regular SMS provider, where we would otherwise not know which transport you meant. Passing it always is harmless and makes the intent obvious.

### Text

```bash
curl -X POST https://api.agentphone.ai/v1/messages \
  -H "Authorization: Bearer $AGENTPHONE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "from_number": "+14788348706",
    "to_number": "+13106222100",
    "channel": "whatsapp",
    "body": "Your table is ready."
  }'
```

### Media

Pass one public HTTPS URL in `media_urls`. WhatsApp takes one attachment per message, so a second URL is a 400 rather than a silent drop.

```json
{
  "from_number": "+14788348706",
  "to_number": "+13106222100",
  "channel": "whatsapp",
  "body": "Here's the receipt.",
  "media_urls": ["https://cdn.example.com/receipt.pdf"]
}
```

Images, video, audio, documents, and stickers all work. Audio cannot carry a caption, so send the text as its own message.

### Quote replies

Set `reply_to_message_id` to an AgentPhone `Message.id` and your message renders with that message quoted above it. Useful when the customer sent five things at once and you are answering the third.

```json
{
  "from_number": "+14788348706",
  "to_number": "+13106222100",
  "channel": "whatsapp",
  "body": "Yes, that one is in stock.",
  "reply_to_message_id": "cmt0u502x000a383d2j6qz9yc"
}
```

The parent must be in the same chat, sent from the same number, and on the WhatsApp channel. Quoting an SMS message from a dual-channel number returns 404, because a WhatsApp message ID means nothing to the cellular network. Meta also refuses to quote anything older than 30 days.

`reply_to_message_id` cannot be combined with `template`, `buttons`, `list`, or `cta`.

### Reply buttons

Up to 3 buttons, 20 characters each.

```json
{
  "from_number": "+14788348706",
  "to_number": "+13106222100",
  "channel": "whatsapp",
  "body": "Confirm your appointment?",
  "buttons": ["Confirm", "Reschedule", "Cancel"]
}
```

Add `media_urls` and the media becomes a header above the text, in the same bubble as the buttons. Image, video, and document headers all work; audio has no header form and returns 422.

```json
{
  "from_number": "+14788348706",
  "to_number": "+13106222100",
  "channel": "whatsapp",
  "body": "*Trail Runner GTX* — $139\nWaterproof, sizes 8-13.",
  "buttons": ["Buy", "Details"],
  "media_urls": ["https://cdn.example.com/shoe.jpg"]
}
```

Repeat that call once per item to build a catalog. WhatsApp has no horizontally swipeable carousel outside of approved carousel templates, so a stack of image-header button messages is the closest thing available on a session message.

### List menus

Up to 10 rows, 24 characters each. Better than buttons when there are more than three choices.

```json
{
  "from_number": "+14788348706",
  "to_number": "+13106222100",
  "channel": "whatsapp",
  "body": "What do you need?",
  "list": { "button": "Choose", "options": ["Book a table", "Order status", "Talk to a human"] }
}
```

The full form adds sections and per-row descriptions:

```json
{
  "list": {
    "button": "Choose",
    "sections": [
      {
        "title": "Support",
        "rows": [
          { "id": "order", "title": "Order status", "description": "Track a shipment" },
          { "id": "human", "title": "Talk to a human" }
        ]
      }
    ]
  }
}
```

List messages take a text header only. Media on a list send is rejected, because Meta will not render it.

### Link buttons

One tappable link on an ordinary message, with no template review.

```json
{
  "from_number": "+14788348706",
  "to_number": "+13106222100",
  "channel": "whatsapp",
  "body": "Your order is on the way.",
  "cta": { "display_text": "Track order", "url": "https://example.com/t/1", "footer": "Takes you to our tracking page" }
}
```

### Reactions

```bash
curl -X POST https://api.agentphone.ai/v1/messages/{message_id}/reactions \
  -H "Authorization: Bearer $AGENTPHONE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"reaction": "love"}'
```

Classic names (`love`, `like`, `dislike`, `laugh`, `emphasize`, `question`) map to emoji, or pass a single emoji directly.

## Templates

Templates are pre-approved message bodies. They are the only way to open a conversation or follow up after the 24-hour window has closed.

### Create one

```bash
curl -X POST "https://api.agentphone.ai/integrations/whatsapp/$CONNECTION_ID/templates" \
  -H "Authorization: Bearer $AGENTPHONE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "order_shipped",
    "language": "en_US",
    "category": "UTILITY",
    "components": [
      {
        "type": "BODY",
        "text": "Your order {{1}} has shipped.",
        "example": { "body_text": [["1234"]] }
      }
    ]
  }'
```

Meta reviews it. `UTILITY` templates usually clear in minutes, `MARKETING` can take longer, and Meta may recategorize based on the content. Any component containing a variable needs an `example` or the submission is rejected.

Names must be lowercase letters, digits, and underscores.

### Check status

```bash
curl "https://api.agentphone.ai/integrations/whatsapp/$CONNECTION_ID/templates" \
  -H "Authorization: Bearer $AGENTPHONE_API_KEY"
```

Each template reports a `status`: `APPROVED`, `PENDING`, `REJECTED`, `PAUSED`, `DISABLED`, and a few more. Only `APPROVED` templates can be sent. A rejection comes with `rejected_reason`.

Filter with `?status=APPROVED`, `?name=`, `?category=`, or `?language=`.

### Send one

```json
{
  "from_number": "+14788348706",
  "to_number": "+13106222100",
  "channel": "whatsapp",
  "body": "",
  "template": { "name": "order_shipped", "language": "en_US", "variables": ["1234"] }
}
```

`body` is not delivered for a template send, since WhatsApp renders the approved content. We keep it only as a fallback label in your message history.

Templates authored with named variables (`{{first_name}}` instead of `{{1}}`) take an object, and the shape has to match how the template was authored or Meta rejects the send:

```json
{ "template": { "name": "welcome", "variables": { "first_name": "Pablo" } } }
```

Pass `header_variables` for a template whose header has variables of its own.

### Template Library

Meta ships a library of pre-written templates. Build from one with `library_template_name` instead of `components`:

```json
{
  "name": "appointment_reminder",
  "language": "en_US",
  "category": "UTILITY",
  "library_template_name": "appointment_confirmation_1",
  "library_template_button_inputs": [
    { "type": "URL", "url": { "base_url": "https://example.com/appt" } }
  ]
}
```

### Delete one

```bash
curl -X DELETE "https://api.agentphone.ai/integrations/whatsapp/$CONNECTION_ID/templates?name=order_shipped&hsm_id=$TEMPLATE_ID" \
  -H "Authorization: Bearer $AGENTPHONE_API_KEY"
```

Deleting by `name` alone removes every language variant under that name, so `hsm_id` is required to scope it to one.

## Receiving

Inbound WhatsApp messages arrive on your [webhook](/documentation/guides/webhooks) like any other channel, with `channel: "whatsapp"`. Nothing extra to configure.

Button and list taps arrive as ordinary inbound messages whose `message` is the label the customer tapped, so the simple path is to treat them as text. What makes them more useful is that each tap also carries `replyTo`, pointing at the message that offered the choice:

```json
{
  "event": "agent.message",
  "channel": "whatsapp",
  "data": {
    "message": "Buy GTX",
    "from": "+13106222100",
    "replyTo": {
      "messageId": "cmt0vbww0000dog1wezu1jh6u",
      "message": "*Trail Runner GTX* — $139\nLightweight, waterproof, size 8-13 in stock."
    }
  }
}
```

That matters for anything with repeated buttons. If you send five product cards each with a **Buy** button, the label alone cannot tell you which product they meant, but `replyTo.messageId` can.

Customer quote replies work the same way: when someone long-presses one of your messages and replies to it, `replyTo` points at the message they quoted.

## Billing

WhatsApp conversation charges are billed by Meta directly to the payment method on your WhatsApp Business Account. They do not draw down AgentPhone credits, and they do not appear in AgentPhone usage.

If your WABA has no payment method, template sends fail with Meta error `131042`. Session messages inside the window are free, so an unpaid WABA can look healthy right up until the first template send.

## Limits

|                    |                                                |
| ------------------ | ---------------------------------------------- |
| Free-form messages | Only within 24h of the customer's last message |
| Outside the window | Approved templates only                        |
| Media              | One attachment per message                     |
| Reply buttons      | 3 max, 20 characters each                      |
| List rows          | 10 max, 24 characters each                     |
| Quote replies      | Parent must be under 30 days old               |
| Message body       | 4096 characters                                |

## Not supported yet

* **Group chats.** WhatsApp's Groups API requires an Official Business Account, which is a separate Meta application.
* **Carousel templates.** Horizontally swipeable cards, each with its own image and buttons. Use a stack of image-header button messages instead.
* **Product and catalog messages.** These need a Meta Commerce catalog attached to your WABA.
* **Flows.** Multi-screen forms authored in Business Manager.