WhatsApp runs on the business's own phone numbers, through the same phone service as calls and texts. A number gets a WhatsApp sender with the business's display name. Messages, replies and broadcasts all go from a number that has WhatsApp on.
Two WhatsApp rules decide every send:
| Rule | What it means |
|---|---|
| The 24-hour window | Free text (and pictures) can go to a customer only within 24 hours of their last WhatsApp message to the business. Outside it, only an approved template can be sent. |
| Templates are approved by WhatsApp | A template is submitted and reviewed by WhatsApp before it can be used. Its text can't be changed after; a change is a new submission. |
Every WhatsApp message reaches a real person and costs money. This skill builds and checks; a person says yes before anything is sent.
Ask first
- Which number? One the business bought through the phone service (Phone & SMS). WhatsApp can't be turned on for a number added from elsewhere.
- The display name customers will see at the top of the chat. It must match the business's name.
- Has the business linked its WhatsApp Business Account? If the phone service says it isn't linked, the person does a one-time sign-up with Meta (step 1). The AI can't.
- What will be sent? Replies to people who write in, or messages the business starts (reminders, updates, offers). Starting a conversation needs a template.
- Have these people agreed to get WhatsApp messages from the business? For a broadcast, if the answer isn't a clear yes, stop.
Steps
1. Phone & SMS: turn WhatsApp on
Find the number: find_records on phone by data.phoneNumber. WhatsApp lives in data.whatsapp, not in capabilities:
data.whatsapp.status | Screen pill | Meaning |
|---|---|---|
none, or off | Off | not turned on (reason may say why) |
pending | Pending approval | WhatsApp is approving the number, usually a few minutes |
on | On | it can send and receive |
failed | Needs attention | reason says what to fix |
call_appengine GET /phone/whatsapp/numbers lists the numbers with WhatsApp on.
To turn it on, after a yes: call_appengine POST /phone/whatsapp/enable/<phone sk> with { "displayName": "<business name>" }. It registers the number as a WhatsApp sender, with the number's own text webhook for replies. A failure still answers 200, with status: "failed" and the reason saved on the number. Read it:
reason (shortened) | What to do |
|---|---|
| Enter the business name customers will see in WhatsApp. | send displayName |
| WhatsApp can only be turned on for numbers bought through the phone service. | use a number bought on Phone & SMS |
| This phone account isn't linked to a WhatsApp Business Account yet… | the person does it: finish Meta's WhatsApp Business sign-up for their phone account, then press Turn on WhatsApp again |
| the display name doesn't match the business | change displayName to the business's name |
GET /phone/whatsapp/status/<phone sk> asks again and saves the answer. A sender WhatsApp has removed goes back to off: This number is not registered for WhatsApp any more. Press Turn on WhatsApp to register it again.
Hands on: the number in + form, with whatsapp.status = on.
Check: on Configuration › Phone & SMS (/phone-system), the number's WhatsApp card shows On, with Display name, Turn on WhatsApp and Check status. Broadcast › Channels › WhatsApp (/broadcast?tab=channels&channel=whatsapp) lists every number with its state.
2. Message Templates: the templates, approved
Anything the business starts, and anything sent more than 24 hours after the customer last wrote, must be an approved template. Make one per kind of message (a booking reminder, an order update, an offer).
Create a message_template with create_record:
| Field | Value |
|---|---|
name | e.g. booking-reminder |
deliveryType | whatsapp (or a list including it) |
text | the body, at most 1,024 characters, with numbered placeholders {{1}}, {{2}}… in order from {{1}}. Named ones ({{firstName}}) are refused. |
whatsapp.category | UTILITY (updates about something they asked for; the default), MARKETING (offers) or AUTHENTICATION (codes). WhatsApp reviews each differently; an offer sent as UTILITY is rejected. |
whatsapp.language | e.g. en (the default) |
whatsapp.header | optional { type: "text" | "image" | "document" | "video", text, mediaUrl } |
whatsapp.footer | optional short line |
whatsapp.buttons | optional [{ type: "quick_reply" | "url" | "phone", text, url, phone }] |
Stop: submitting sends the template to WhatsApp for review. Show the text and category, then wait for yes. Then call_appengine POST /crm/message-template/whatsapp/submit/<template sk> with { "samples": { "1": "Ada", "2": "Friday 3 pm" } }. Real-looking samples help it pass. Without them, each placeholder is sent as "example n".
whatsapp.approval.statusbecomespending, thenapprovedorrejected(withreason). A template WhatsApp pauses or disables readsrejectedwith its reason.- Resubmitting an unchanged rejected template is refused (409, "…Change the template before submitting it again."). Change the words, then submit again.
GET /crm/message-template/whatsapp/status/<template sk>asks where it stands now.
Hands on: each approved template's sk (or name).
Check: open it on Message Templates (/message-template). The WhatsApp section shows Pending approval, Approved or Rejected, with Submit for WhatsApp approval and Refresh status.
3. IVR & Phone System: STOP replies
WhatsApp replies go through the number's SMS routing, the same as texts. A STOP is recorded only when the number has an enabled sms_routing with optOut.enabled. The words are STOP, UNSUBSCRIBE, CANCEL, END and QUIT by default; START, SUBSCRIBE or YES undo it. A STOP from one number counts for both texts and WhatsApp, for a year. Check it as in SMS broadcast to a group, step 3. Setting one up changes how the number answers, so it is the person's decision.
The same routing's auto-replies and keywords answer WhatsApp messages too. If it has a text auto-reply, WhatsApp customers get it.
Hands on: whether STOP replies are recorded for this number.
4. Inbox: one message, or a reply
Customers' WhatsApp messages land in Inbox (/inbox) as conversations on the WhatsApp channel, saved as message records (deliveryType: "whatsapp", direction: "inbound", with profileName, their WhatsApp name).
Before writing, check the window: call_appengine GET /crm/inbox/whatsapp-window?to=<customer +number>&from=<business +number> answers { open, lastInboundAt, closesAt }. The window counts only messages to that business number. Without from, it counts any of the business's numbers.
- Open: free text, with pictures if wanted.
- Closed: only an approved template (step 2). Don't send free text: it is refused with This customer last wrote more than 24 hours ago — WhatsApp only allows an approved template now. (
reason: whatsapp_window_closed).
Stop: show the customer, the business number, the words (or template and its values), and the cost, then wait for yes. Then call_appengine POST /crm/inbox/update?send=true with:
{ "data": {
"deliveryType": "whatsapp",
"to": ["+12145550142"],
"from": "+12145550100",
"text": "Hi Ada, your table for 4 is ready.",
"media": [{ "url": "https://…/menu.jpg" }]
} }For a template, send "template": { "contentSid": "<whatsapp.contentSid>", "variables": { "1": "Ada" } } instead of text. Staff send from the Inbox; the same call is what its composer uses. A number without + takes the sending number's country code.
What one message costs on the platform's shared phone account (the business's own rates are on Service rates):
| Kind | Default price before markup | With the default 100% markup |
|---|---|---|
| free text inside the window | $0.005 | $0.01 |
| a template | $0.03 | $0.06 |
Hands on: the message's sk.
Check: the message shows in the conversation, pending then sent. A late failure is written onto the message (status: "failed", its reason in statusHistory). Common ones, in the person's words:
| Reason | Meaning |
|---|---|
| This number isn't turned on for WhatsApp yet… | step 1 isn't finished |
| This customer has stopped receiving business messages on WhatsApp… | reach them by text or email instead |
| …WhatsApp only allows an approved template now. | the window closed before it went |
5. Broadcast: a WhatsApp draft to a group
Find the group as in SMS broadcast to a group, step 1. Count it with POST /broadcast/audience/count and { "lists": ["<group sk>"], "channel": "whatsapp" }, and ask who is left out with POST /broadcast/audience/people and "show": "out". The same rules as SMS apply: one message per number, no number, STOP, and US/Canada for a number without +.
Create an email_broadcast with create_record:
| Field | Value |
|---|---|
name | the campaign's name |
channel | whatsapp |
whatsappConfig.templateId | the approved template's sk or name |
whatsappConfig.variables | e.g. { "1": "{{firstName}}" }, filled per person |
whatsappConfig.from | the business number, + form |
recipientMethod / selectedLists | lists / ["<group sk>"] |
status | draft |
A broadcast with a template sends the template to everyone. A broadcast with only a message (whatsappConfig.message or content) sends it only to people whose window is open; everyone else fails with whatsapp_window_closed. For a group, always use a template.
Never set status to pending or scheduled, add a schedule, or call POST /broadcast/<id>/send.
Hands on: the broadcast's sk.
Check: read it back (status: draft, type: whatsapp) and open it (/broadcast?openId=<sk>). Its WhatsApp section shows WhatsApp template, Fill in the template and Message for recent conversations (optional).
The composer says the optional message goes to people who wrote in the last 24 hours. It doesn't: when a template is set, everyone gets the template and the message is not used. Don't promise the person otherwise.
Stop: send the broadcast
Show the template exactly as each person will see it (with a real name filled in), the number it comes from, the group and its count, who is left out and why, whether STOP is recorded, and the cost: people × the template price. Confirm these people agreed to get WhatsApp messages. A person presses Settings & Send → Send Now (or schedules it). If they say "send it" in chat, repeat the count and cost, and send only after a clear yes.
6. The AI on WhatsApp (optional)
- An AI assistant answers WhatsApp when the number's SMS routing hands messages to it, or when the business has no routing at all and an assistant listens on
whatsapp. Set it up with AI assistant for the website chat or a phone line. - An AI employee listening on
whatsappgets each inbound message as a job (WhatsApp from(+1…) ), with pictures attached. See Give an AI employee a standing job. - Their tools:
reply_to_conversationanswers on the channel the customer used, andsend_whatsapp(to,messageortemplateIdwithvariables,mediaUrls,from) starts one. Both obey the window: outside it, they need an approvedtemplateId.
When it's done
Tell the person:
- the number and its WhatsApp state, and the display name;
- each template, its category and approval state;
- whether STOP replies are recorded;
- what was sent, to whom, and what it cost; or the broadcast waiting as a draft, its count and cost;
- what's waiting for them: Meta's sign-up, a template in review, pressing Send.
What it can't do yet
- WhatsApp only through the phone service. Connecting a WhatsApp Business number directly through Meta isn't supported. WhatsApp messages Meta sends straight to Appmint are ignored.
- STOP needs an SMS routing. Without one with opt-out on, a STOP isn't recorded. A broadcast leaves out recorded STOPs; a single message from the Inbox or the AI doesn't check them.
- No consent record. Nothing stores that a person agreed to WhatsApp messages.
- A broadcast's "message for recent conversations" isn't sent when a template is set (see the warning in step 5).
- Templates can't be edited. A change is a new submission and a new review.
Don'ts
- Don't send free text when the window is closed, and don't retry a refused send as free text.
- Don't put a marketing offer in a
UTILITYtemplate. - Don't use named placeholders like
{{firstName}}in a template's text. Map them invariablesinstead. - Don't send, submit a template or turn WhatsApp on without a yes.
- Don't message people the person hasn't said agreed to it.