Send on WhatsApp
Reply to customers on WhatsApp from your code, inside WhatsApp's 24-hour reply window.
What you'll build
Code that answers a person who wrote to your WhatsApp business number, checks the reply window first, and handles a closed window.
How WhatsApp differs
WhatsApp only lets a business message a person freely for 24 hours after that person's last message. This is the reply window. Outside it, WhatsApp requires a pre-approved message template.
422 operation_not_supported (details.reason: "template_sends_not_available").Prerequisites
- A secret API key with
messages:sendandmessages:read. See Authentication and API keys. - A WhatsApp business number connected in the dashboard under Channels. It is a Binding, your sender on WhatsApp.
- A conversation someone started by writing to that number. Its id comes from a
message.receivedevent (webhooks or the event stream) or fromGET /v1/conversations. - curl, Node.js 18 or later with
@voicelayer/api, or Python 3.9 or later withvoicelayer.
Save the conversation id first:
export CONVERSATION_ID=<the conversation's id>Step 1: Check the reply window
GET /v1/conversations/{id} returns the conversation. On WhatsApp it has a window: open says whether you may reply, and closesAt says until when.
curl "https://api.vlayers.ai/v1/conversations/$CONVERSATION_ID" \
-H "Authorization: Bearer $VOICELAYER_API_KEY"{
"conversation": {
"id": "0192f5b0-7c3e-7a51-9b2d-4e8f1a6c3d21",
"channel": "whatsapp",
"controller": "api",
"window": { "open": true, "closesAt": "2026-10-21T15:04:05.000Z" }
}
}Step 2: Reply in the conversation
POST /v1/conversations/{id}/messages sends into the conversation. The channel, your WhatsApp number and the person come from the conversation.
curl -X POST "https://api.vlayers.ai/v1/conversations/$CONVERSATION_ID/messages" \
-H "Authorization: Bearer $VOICELAYER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": {
"type": "text",
"text": "Yes, we have a table at 7pm. Shall I book it?"
}
}'{
"message": {
"id": "0192f5b0-8d1f-7b62-8c3e-5f9a2b7d4e32",
"conversationId": "0192f5b0-7c3e-7a51-9b2d-4e8f1a6c3d21",
"channel": "sms",
"direction": "outbound",
"status": "queued",
"text": "Yes, we have a table at 7pm. Shall I book it?",
"media": [],
"from": "+14155550100",
"to": "+15550123",
"bindingId": null,
"metadata": {},
"error": null,
"createdAt": "2026-10-20T15:04:05.000Z",
"parts": [
{
"id": "0192f5b0-8d1f-7b62-8c3e-5f9a2b7d4e32",
"status": "queued"
}
]
}
}A long text is sent as several messages (up to 4,096 characters each).
Step 3: Handle a closed window
If the window has closed, the reply is refused with 409 window_closed and nothing is sent. Wait for the person to write again: their next message reopens the window for another 24 hours. Tell your users or your agent to ask for what they need while the window is open.
Test it
In test mode, a reserved number plays the customer. Without a connected WhatsApp number, your free test sender receives it, but it has no reply window, so Step 1 shows no window. To test the 24-hour window itself, connect your WhatsApp number and name it in to:
curl -X POST https://api.vlayers.ai/v1/test/inbound \
-H "Authorization: Bearer $VOICELAYER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"channel": "whatsapp",
"from": "+15550124",
"text": "Hi, can I change my delivery address?"
}'Save the returned conversationId as CONVERSATION_ID and run Steps 1 and 2. The reply goes to a simulated carrier and costs nothing. See Test mode.
Templates you have
GET /v1/channels/whatsapp/templates lists the templates Meta approved for a WhatsApp number, with their variables. It is read only for now; use it to prepare for template sends.
curl "https://api.vlayers.ai/v1/channels/whatsapp/templates?bindingId=$BINDING_ID" \
-H "Authorization: Bearer $VOICELAYER_API_KEY"Common errors
| Error | Status | How to fix |
|---|---|---|
window_closed | 409 | Wait for the person to write again. Template sends, the way to reach someone outside the window, are not available yet (operation_not_supported). |
operation_not_supported | 422 | Send text instead (inside the conversation's window, on windowed channels). |
capability_not_ready | 409 | Fix the connection in the dashboard (Channels) using details.remediation when present, then send again. |
no_sender | 409 | Connect a sender for the channel in the dashboard (for email, create a mailbox), or name one in from. |
not_found | 404 | Check the id, and that the key belongs to the workspace (and covers the channel) that owns it. |
recipient_not_allowed | 403 | Reply in an existing conversation instead, or wait for the person to write first. |
Next steps
- Conversations and handoff: let a person take over a WhatsApp conversation.
- Receive with webhooks: get the
message.receivedevent that starts each conversation. - Send on Telegram.