WhatsApp lines

View as Markdown

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.

WhatsApp is in beta and enabled per account. Talk to us 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:

$dial number purchase --imessage --whatsapp

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

$dial number whatsapp pn_123

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.

Both paths are asynchronous. Poll List phone numbers until the number’s whatsapp.status is ready; sending before then is rejected.

What the number tells you

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

1{
2 "id": "pn_123",
3 "capabilities": ["sms", "call", "imessage", "whatsapp"],
4 "setupStatus": "ready",
5 "whatsapp": {
6 "status": "ready",
7 "error": null,
8 "retryAvailableAt": null
9 }
10}
  • capabilities gains whatsapp only once the channel actually works. 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 a few minutes 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.

Choose the channel

channel is accepted both when sending a message (POST /api/v1/messages) and on a typing indicator (POST /api/v1/typing, or dial typing start):

1{ "channel": "imessage" } // the iMessage rail, with its automatic RCS/SMS fallback
2{ "channel": "whatsapp" } // the WhatsApp rail
$dial message --to +14155550123 --body "Hello over WhatsApp" --from-number pn_123 --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

Outbound messages are text-only. A send with mediaUrls or media on channel: "whatsapp" is rejected with 400“This WhatsApp number sends text only.”

Inbound attachments are delivered normally. A photo someone sends to your WhatsApp line arrives as a regular media array on the message.received event, hosted by Dial like any other attachment. The limitation is on the way out only.

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 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, both delivery and read mean every recipient.

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.

Typing indicators inside a group aren’t supported yetchannel: "whatsapp" typing works for a one-to-one conversation only. It’s on the roadmap.

Next