Inbox
Receive and reply to WhatsApp conversations through the inbox API, send templates, interactive and commerce messages, and track delivery with webhooks.
WhatsApp conversations live in the same inbox API as every other platform.
- List with
GET /v1/inbox/conversations. - Reply with
POST /v1/inbox/conversations/{conversationId}/messagesandaccountIdplusmessage.
Two WhatsApp rules sit on top:
- The 24-hour window. Free-form messages are allowed for 24 hours after the customer's last message. Outside that window, and to open a conversation, you send an approved template.
- Delivery status. Delivery is reported per message as sent, delivered and read.
Direct messages
| Feature | Supported |
|---|---|
| List conversations | |
| Fetch messages | |
| Send text messages | |
| Send attachments | (images, videos, documents, audio) |
| Send voice notes | with voiceNote: true on an audio attachment |
| Send interactive messages | (reply buttons, list, CTA URL, flow) with interactive or buttons |
| Send commerce messages | (single product, product list, catalog, carousel) with interactive; see Commerce messages |
| Send location and contact cards | with location and contacts |
| Reply to a specific message | with replyTo (the quoted message's platformMessageId) |
| React to a message | (emoji) with Add message reaction |
| Typing indicator | with Send typing indicator |
| Archive and unarchive | |
| Comments | (WhatsApp has none) |
Attachments:
| Type | Max size |
|---|---|
| Image | 5 MB |
| Video | 16 MB |
| Document | 100 MB |
| Audio | 16 MB |
Formats are on Media requirements.
Voice notes. A voice note (voiceNote: true) renders with the waveform, autoplay and profile-picture bubble. The file must be .ogg with the Opus codec, mono. Dashboard microphone recordings are transcoded on the server.
Without the flag, an Opus file still shows a microphone icon, but as a plain audio attachment.
Send a template
A template opens a conversation, or continues one whose 24-hour window has closed.
To open one, call POST /v1/inbox/conversations with:
- the phone in
participantId(digits, country code included, no+) templateName,templateLanguageandtemplateParams
templateParams is one flat array in the order the variables appear across the template: text-header variables first, then body variables, then dynamic URL buttons.
import Zernio from '@zernio/node';
const zernio = new Zernio();
const accountId = '66b2e19d8c3f5a7e9d0b1c2d';
const { data: opened } = await zernio.messages.createInboxConversation({
body: {
accountId,
participantId: '13105551234',
templateName: 'order_confirmation',
templateLanguage: 'en',
templateParams: ['Ana', 'ORD-12345']
}
});
const conversationId = opened.data.conversationId;Response (201):
{
"success": true,
"data": {
"messageId": "wamid.HBgLMTMxMDU1NTEyMzQVAgARGBI5QTNEMEY3RjQ4RjE2QjA3QzYA",
"conversationId": "66c3d2ae7b4f6c8d0e1f2a3b",
"participantId": "13105551234"
}
}data.conversationId is the id the list endpoint returns for the same conversation.
Inside an existing conversation, send the template through POST /v1/inbox/conversations/{conversationId}/messages with template.elements. One element carries the template reference in Meta's send shape:
{
"accountId": "66b2e19d8c3f5a7e9d0b1c2d",
"template": {
"elements": [{
"name": "order_confirmation",
"language": "en",
"components": [{
"type": "body",
"parameters": [
{ "type": "text", "text": "Ana" },
{ "type": "text", "text": "ORD-12345" }
]
}]
}]
}
}| Field | Use |
|---|---|
templateButtonParams | Fills a button that takes its value at send time (url suffix, copy_code coupon, flow token) |
headerMedia | Swaps the header image, video or document for this send |
headerLocation | Required for a template whose header is a location |
Without a template. A utility message without a template is possible through Meta Direct Send: category: "utility" with message and no templateName.
The 400 codes DIRECT_SEND_NOT_ELIGIBLE, DIRECT_SEND_LIMITED and DIRECT_SEND_BLOCKED say when the WABA cannot.
Commerce messages
Send products from your Meta catalog and swipeable carousels with POST /v1/inbox/conversations/{conversationId}/messages and the interactive field.
Like every interactive message, these are session messages, so the 24-hour window must be open.
Product, product list, catalog and product-carousel messages need a Meta catalog connected to your WABA in Commerce Manager. Media carousels need no catalog.
A single product card. body is optional; the product image is the header.
{
"accountId": "66b2e19d8c3f5a7e9d0b1c2d",
"interactive": {
"type": "product",
"body": {"text": "Back in stock"},
"action": {
"catalog_id": "194836987003835",
"product_retailer_id": "sku-blue-tshirt"
}
}
}Orders. When a customer submits a cart from any commerce message, message.received fires with metadata.order:
catalog_id- an optional customer note in
text product_itemswithproduct_retailer_id,quantity,item_priceandcurrency
Product questions. When a customer taps "Message business" on a product, their message carries metadata.referredProduct with the catalog_id and product_retailer_id they asked about.
Both render in the dashboard inbox, whose composer can send products, catalogs and media carousels.
Meta Business Agent standby
On a number with Meta Business Agent enabled, the agent can answer conversations on Meta's side while Zernio only observes.
- Inbound messages still arrive as
message.receivedwithmetadata.standby: true. - The agent's replies arrive as
message.sentwithsource: "meta_business_agent". - The conversation's
threadControlin List inbox conversations says who answers now.
Sending any message takes control back from the agent, so a bot that replies to every inbound message must skip standby ones.
To hand a conversation back to the agent without sending, or to take it before an operator reads the thread, call Change who answers a conversation with action: "release" or "take".
Every change fires conversation.control_changed. It carries control.ownerAppId when another partner app took the conversation and Meta names it.
Ice breakers and slash commands
A number's conversational automation is what a person sees when they open a chat:
- Up to 3 ice breaker
prompts, 80 characters each. - Up to 30 slash
commands, shown when they type/.command_nameis up to 30 letters, digits or underscores, unique, without the slash.command_descriptionis up to 256 characters. enable_welcome_message, which makes Meta send arequest_welcomeevent the first time a person opens a chat. Zernio does not forwardrequest_welcomeas a webhook event.
Read it with GET /v1/whatsapp/conversational-automation?accountId=.
Change it with Set ice breakers and commands. Only the fields you send are changed.
Clear everything with DELETE.
curl -X POST "https://zernio.com/api/v1/whatsapp/conversational-automation" \
-H "Authorization: Bearer $ZERNIO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"accountId": "66b2e19d8c3f5a7e9d0b1c2d",
"prompts": ["Track my order", "Talk to sales"],
"commands": [{ "command_name": "help", "command_description": "Get help with your order" }]
}'A tapped prompt arrives as a normal message.received carrying its text, so an inbound_message workflow with that text as a keyword answers it.
Webhooks
WhatsApp emits the full message and delivery lifecycle:
| Event | When it fires |
|---|---|
message.received | A new incoming WhatsApp message |
message.sent | An outgoing message is sent. source is cloud_api for sends through Zernio, whatsapp_business_app for sends from the phone on a coexistence number and meta_business_agent for replies Meta Business Agent wrote |
message.delivered | An outgoing message reaches the recipient |
message.read | The recipient reads an outgoing message |
message.failed | An outgoing message fails to deliver; the payload's error object carries the Cloud API error code (for example 131026, message undeliverable) |
message.deleted | The business deletes a sent message with DELETE /v1/inbox/conversations/{conversationId}/messages/{messageId} |
reaction.received | A participant adds or removes an emoji reaction. reaction.emoji is empty on removal, because the Cloud API does not report which emoji was removed |
conversation.control_changed | Meta Business Agent takes over a conversation, hands it back, or another partner app takes it (standby) |
Subscribe with Create webhook settings. Payloads are on inbox webhooks.
The Cloud API does not expose incoming-message edits or recipient-side unsends. message.edited and an incoming message.deleted are never emitted for WhatsApp.
If it fails
WhatsApp caps how many messages one recipient can receive in a short window and rejects the excess with code 131056:
{
"error": "Too many messages sent to this recipient",
"code": "131056"
}Pace sends to a single recipient at about 10 per minute. Other recipients are unaffected, so parallelize across recipients rather than flooding one.
A 400 with code TEMPLATE_REQUIRED on POST /v1/inbox/conversations means the message tried to open a conversation without a template.
Meta's other codes (131047 the closed 24-hour window, 132001 template not found) are on Common errors.
Retries. Zernio does not retry a send on its own, because a send is not idempotent. Pass an Idempotency-Key header to make your own retries safe.
Every error uses the envelope in error handling.
Related
- Templates: the messages that open a conversation.
- Broadcasts: the same template to many recipients.
- Flows: forms and booking screens sent as interactive messages.
- Inbox API and Send message: every field.
- Messages pricing: what outbound messages cost.