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

# WhatsApp lines

> Add WhatsApp to a Dial iMessage number, and choose which channel each message goes out on.

WhatsApp is a **channel on an iMessage line**, not a separate kind of number. A line that carries it can send iMessage *and* WhatsApp from the same phone number — so unlike every other channel, you say which one you mean on each message.

> **Note**
>
> **WhatsApp is in beta and enabled per account.** [Talk to us](/documentation/reference/support) to request access.
>
> Until it's enabled, the WhatsApp provisioning endpoints answer **`404`** rather than `403`. That's deliberate: to an account without access the capability doesn't exist, which is also what the dashboard shows — the number's menu has no such item. A `403` would advertise something you can't reach and can't ask for.

## Add WhatsApp to a line

Two entry points. Buy a new iMessage number with WhatsApp included:

**`CLI`**

```bash title="CLI"
dial number purchase --include-imessage --whatsapp \
  --inbound-instruction "You are my receptionist." \
  --explicit-programmatic-consent "User modal confirmation"
```

**`cURL`**

```bash title="cURL"
curl -X POST https://api.getdial.ai/v1/numbers \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"capabilities":["sms","call","imessage"],"whatsapp":true,"explicitProgrammaticConsent":"User modal confirmation"}'
```

…or add it to an iMessage number you already hold:

**`CLI`**

```bash title="CLI"
dial number whatsapp pn_123
```

**`cURL`**

```bash title="cURL"
curl -X POST https://api.getdial.ai/v1/numbers/pn_123/whatsapp \
  -H "Authorization: Bearer sk_live_..."
```

`whatsapp: true` is only valid alongside `capabilities: ["sms","call","imessage"]` — WhatsApp rides on an iMessage line, so there's no standard-number combination to ask for.

The two paths differ in how long WhatsApp takes:

* **A new number arrives ready.** Provisioning with `whatsapp: true` hands you a number whose WhatsApp is already set up and warmed: `whatsapp.status` is `ready` in the response itself. `areaCode` is not honored for these numbers. When no WhatsApp number is available right now the request is refused with `503` `whatsapp_unavailable` — nothing was bought or charged, so retrying shortly is safe.
* **Connecting your own number takes hours.** WhatsApp is registered on the number (`provisioning`), and then the number is **warmed up** (`warming_up`) for about 6 hours before its first use, and only then becomes `ready`. Wait for the [`number.status_changed`](/api-reference/events/number-status-changed) event that reports `ready` — with a long timeout — rather than polling tightly. If the warm-up cannot finish, the track ends `failed` with a reason you can show.

Sending on the channel is rejected until `whatsapp.status` is `ready`.

## What the number tells you

A WhatsApp-capable line reports two things nothing else does:

```json
{
  "id": "pn_123",
  "capabilities": ["sms", "call", "imessage", "whatsapp"],
  "setupStatus": "ready",
  "whatsapp": {
    "status": "ready",
    "error": null,
    "retryAvailableAt": null,
    "autoRetryScheduled": false
  }
}
```

* **`capabilities` gains `whatsapp` only once the channel actually works** — at `ready`, never during `provisioning` or `warming_up`. Before that the line reports `["sms","call","imessage"]`, because a capability you can see but can't use is worse than one that shows up late.
* **`whatsapp.status` is independent of `setupStatus`.** They're separate tracks on the same line: voice caller-ID can be `ready` while WhatsApp is `failed`, and the other way round. One status field could never carry both, so there are two.
* **`whatsapp.retryAvailableAt`** is when the channel will accept another verification attempt, or `null`. It's the one failure you can neither fix nor retry around — if it's set and in the future, a retry is refused until it lifts. Show the time rather than a button that won't work.
* **`whatsapp.autoRetryScheduled` tells you whether to wait or to act.** A cooling-off period is the one failure that resolves on its own clock, so Dial waits it out and re-runs setup for you — a bounded number of times, after which it stops and leaves the line to you. While this is `true`, there is nothing to do: say a retry is coming and let it. When it's `false` and `retryAvailableAt` is `null`, the next attempt is yours to ask for.
* **`whatsapp.status: "banned"` means WhatsApp withdrew a line that was working.** It is not a setup failure — `previousStatus` is `"ready"` — and it is permanent: `retryAvailableAt` is always `null`, no retry helps, and `whatsapp` leaves `capabilities`, so sends on that channel are refused from then on. The number's other channels are unaffected. Registering WhatsApp on the number again is the only route back. You learn about it through [`number.status_changed`](/api-reference/events/number-status-changed), which is why that event is worth consuming for the life of a number and not only while one is being set up.

## Choose the channel

`channel` is accepted both when [sending a message](/documentation/capabilities/send-an-sms) (`POST /api/v1/messages`) and on a typing indicator (`POST /api/v1/typing`, or [`dial typing start`](/documentation/cli/commands#typing-start)):

```jsonc
{ "channel": "imessage" }   // the iMessage rail, with its automatic RCS/SMS fallback
{ "channel": "whatsapp" }   // the WhatsApp rail
```

**`CLI`**

```bash title="CLI"
dial message --to +14155550123 --body "Hello over WhatsApp" --from-number pn_123 --channel whatsapp
```

**`cURL`**

```bash title="cURL"
curl -X POST https://api.getdial.ai/v1/messages \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"to":"+14155550123","fromNumberId":"pn_123","body":"Hello over WhatsApp","channel":"whatsapp"}'
```

**Omitting `channel` keeps the line's own default**, so nothing you already send changes: a standard number sends SMS, an iMessage number sends iMessage. You only need `channel` on a line that carries both rails — and passing it explicitly is a good habit there, because "the default" is the one thing that could change under you.

Two ways it's rejected, both `400`:

* `channel: "whatsapp"` on a line whose WhatsApp track isn't live — *"WhatsApp isn't connected on this number."*
* `channel: "imessage"` on a number with no iMessage rail (a standard SMS/call number) — the number can't do it, so the ask is refused rather than quietly delivered as an SMS.

## What WhatsApp does differently

**Media goes out one attachment at a time.** A WhatsApp send takes the same `media` / `mediaUrls` fields as every other channel, but carries **at most one** attachment — more is rejected with `400` (*"A WhatsApp message can carry at most one attachment."*). Supported types match the rest of [Send a message](/api-reference/rest-api/messages/send-message): images, audio, video, PDF, vCard, and iCalendar.

**`CLI`**

```bash title="CLI"
dial message --to +14155550123 --from-number pn_123 --channel whatsapp \
  --media ./party.jpg --body "See you Saturday"
```

**`cURL`**

```bash title="cURL"
curl -X POST https://api.getdial.ai/v1/messages \
  -H "Authorization: Bearer sk_live_..." \
  -F to=+14155550123 -F fromNumberId=pn_123 -F channel=whatsapp \
  -F body="See you Saturday" -F media=@party.jpg
```

* **A caption only rides on an image or a video.** WhatsApp shows no caption on audio or a document, so a `body` alongside one is rejected with `400` rather than sent without its text. Send the text as its own message.
* **A lone audio attachment is a voice note** by default. Set `forceAudioFile: true` (`--force-audio-file`) to deliver it as a regular audio file instead — the same flag as on iMessage numbers.
* **A vCard arrives as a document**, not a native contact card.

**Inbound attachments are delivered normally.** A photo someone sends *to* your WhatsApp line arrives as a regular `media` array on the [`message.received`](/api-reference/events/message-received) event, hosted by Dial like any other attachment.

**Replies and reactions are native.** [Reply to a message](/api-reference/rest-api/messages/reply-to-message) works on WhatsApp in both forms, in a one-to-one conversation or a [group](/documentation/capabilities/groups):

**`CLI`**

```bash title="CLI"
dial message reply msg_123 --react "🎉"
dial message reply msg_123 --body "Count me in"
```

**`cURL`**

```bash title="cURL"
curl -X POST https://api.getdial.ai/v1/messages/msg_123/reply \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"reaction":"🎉"}'
```

* **A `reaction`** lands on the target message itself, exactly as one added in the app. Any single emoji works, and the six reaction names map to their emoji — 👍 `like`, ❤️ `love`, 👎 `dislike`, 😂 `laugh`, ‼️ `emphasize`, ❓ `question`.
* **A `body` reply** is a native quoted reply — the target is quoted above your text. A reply carries text only; to send media, send a new message into the conversation.
* **The target can be in either direction** — a message you received or one you sent — but not a reaction.

**Delivery status starts unconfirmed and firms up.** A WhatsApp send comes back with `deliveryState: "unconfirmed"` (`status: "unknown"`) — the channel assigns the message its identity at send time, so there's nothing honest to report yet. Receipts then arrive as [`message.status_changed`](/api-reference/events/message-status-changed) events and the state climbs from there.

**Reads are reported.** Unlike SMS, WhatsApp tells you when a message was read, so `readState` and `readAt` are meaningful on this channel. In a [group](/documentation/capabilities/groups), both delivery and read mean *every* recipient — on this channel that verdict arrives already tallied for you.

**One send at a time per line.** A WhatsApp line sends messages one after another. A second send that arrives while the first is in flight is **refused, not queued** — so you get an error immediately and retry, rather than waiting behind something you can't see.

**Messages from one number are spaced.** WhatsApp suspends numbers that behave like software, so consecutive sends on a line are separated by at least a second. A send that arrives inside that gap is rejected with `429` and a `Retry-After` telling you when to come back — your request is never held open waiting.

**Volume and unanswered replies are capped.** Each WhatsApp number has a daily cap, a per-recipient cap, and a limit of three unanswered messages to one person. Over any of them, a send is rejected with `429` (`rate_limited`). See [WhatsApp sending limits](/documentation/reference/usage-and-limits#whatsapp-sending-limits) before you plan a campaign.

**WhatsApp can limit a number's access.** While it does, the number can't start a conversation with someone it hasn't talked to before: a send or a typing indicator toward them is rejected with `403` (`whatsapp_limited`) and nothing is sent. Replies to people who have messaged the number still go through. See [Limited WhatsApp access](/documentation/reference/errors#limited-whatsapp-access).

> **Note**
>
> **A WhatsApp text message types before it's sent.** The recipient sees "typing…" for about 200-250 ms per character, up to about 30 seconds however long the message is, and then the message arrives, the way it would from a person on their phone. The same goes for a text reply. The send returns once the message is out, so it takes that much longer: about 7 seconds for a 30-character message.
>
> **Typing indicators work inside a group too** — address it with `groupId` instead of `toNumber`, and every member sees it. See [Group conversations](/documentation/capabilities/groups#show-a-typing-indicator-in-a-group).

## Next

#### [Group conversations](/documentation/capabilities/groups)

Send to and read from group conversations.

#### [Manage phone numbers](/documentation/platform/manage-phone-numbers)

Provision and configure your lines.

#### [Sending limits](/documentation/reference/usage-and-limits#whatsapp-sending-limits)

Pacing, daily caps, and reply-rate limits.