WhatsApp lines
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:
…or add it to an iMessage number you already hold:
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:
capabilitiesgainswhatsapponly 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.statusis independent ofsetupStatus. They’re separate tracks on the same line: voice caller-ID can bereadywhile WhatsApp isfailed, and the other way round. One status field could never carry both, so there are two.whatsapp.retryAvailableAtis when the channel will accept another verification attempt, ornull. 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):
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 yet — channel: "whatsapp" typing works for a one-to-one conversation only. It’s on the roadmap.