Sending WhatsApp messages
Send template messages safely, follow their statuses, and understand costs, limits, and retention.
Send a WhatsApp message with one request to POST /v1/messaging/messages, the endpoint every messaging channel shares, with channel set to whatsapp. You need a connected sender, an approved template, and an API key with the whatsapp.send permission.
Compose a message
channelrequiredstringwhatsapp.fromrequiredstringThe phone number of your connected WhatsApp number, in international format, such as
+213555000111.torequiredstringThe recipient's phone number in international format, with
+and the country code, such as+213555123456.template.idrequiredstringThe Template ID of an approved template of that sender's WhatsApp Business Account, such as
wat_….template.variablesoptionalobjectValues for the template's
headerandbodyvariables. See Variables.
{
"channel": "whatsapp",
"from": "+213555000111",
"to": "+213555123456",
"template": {
"id": "wat_example",
"variables": {
"body": {
"1": "Amina",
"2": "A-1234"
}
}
}
}The template decides the language, the category, and everything the recipient sees except the variable values.
Recipients
Each request sends one message to one recipient. To message several customers, send one request per recipient, each with its own idempotency key.
Write numbers in E.164 format: +, the country code, then the number without spaces, dashes, or a leading 0. 0555 12 34 56 in Algeria becomes +213555123456. The same format applies to from.
The recipient must have a WhatsApp account and must have agreed to receive your messages. WhatsApp doesn't let businesses check in advance whether a number uses WhatsApp: a message to a number without WhatsApp is accepted, then becomes failed.
Idempotency keys
Every API send requires an Idempotency-Key header. It guarantees that one operation sends at most one message, even when your request times out and you retry it.
- Use a key that identifies the operation in your system, such as
order-A-1234-shipped. Up to 200 characters. - Retrying with the same key and the same request returns the original message as it is now, without sending again or charging again. Its status may have moved on, for example to
delivered. - Reusing a key for a different message returns
409with the codewhatsapp_idempotency_key_reused. - Keys are scoped to your project.
Store the key with the operation that triggers the message, so a retry after a crash or deploy uses the same key. See Retry safely.
Message statuses
A successful send returns 200 OK with the message, usually with the status accepted. WhatsApp then reports how far the message got.
| Status | Meaning | Final |
|---|---|---|
sending | The message is being handed to WhatsApp. | No |
accepted | WhatsApp accepted the message. It is not delivered yet. | No |
sent | WhatsApp sent the message to the recipient's phone. | No |
delivered | The message reached the recipient's phone. | No |
read | The recipient opened the message. | Yes |
failed | WhatsApp refused or could not deliver the message. failure_code and failure_reason say why. | Yes |
uncertain | WhatsApp didn't confirm whether it accepted the message. See below. | No |
A status only moves forward: accepted, sent, delivered, read. Some steps can be skipped; a message can go straight from sent to read. read is reported only for recipients who share read receipts, so many messages stay delivered. A message that failed after it was delivered keeps its delivered status.
Each status has its own timestamp on the message: accepted_at, sent_at, delivered_at, read_at, and failed_at.
When a message is uncertain
If WhatsApp doesn't answer in time, the message may or may not have been sent. Nexiom Connect never resends it on its own, so the recipient never gets it twice. The response is still 200 OK, with the status uncertain.
When WhatsApp later reports a status for the message, it moves on from uncertain, and the matching webhook fires. If it stays uncertain, check with the recipient before sending it again with a new idempotency key: retrying with the same key returns the same uncertain message and sends nothing.
Handle errors
A refused request sends nothing and charges nothing, except where noted.
| Response | Code | What to do |
|---|---|---|
400 | invalid_argument and validation details | Fix the request: channel, field names, the from or to format, or the Idempotency-Key header. |
400 | whatsapp_template_variable_missing, whatsapp_template_variable_unknown | Send exactly the template's variables, each with a non-empty value. |
403 | not_allowed | Use a key with the whatsapp.send permission. With the code FEATURE_NOT_AVAILABLE, WhatsApp is not turned on for your organization. |
404 | whatsapp_number_not_found, whatsapp_sender_removed | Check the from phone number, and that the number is still connected in this project. |
404 | whatsapp_template_not_found | Check the Template ID, and that the template belongs to the sender's WhatsApp Business Account. |
409 | whatsapp_idempotency_key_reused | Use a new key for a new message. |
422 | whatsapp_number_not_ready | Open the sender's page: the number is not connected or WhatsApp is limiting it. |
422 | whatsapp_template_not_approved, whatsapp_template_not_supported | Wait for approval, or use a template Nexiom Connect can send. |
422 | whatsapp_message_rejected | WhatsApp refused the message. The message is stored as failed with WhatsApp's reason. |
422 | whatsapp_provider_unavailable | WhatsApp is busy. Retry later with a new idempotency key; the refused message is stored as failed. |
429 and 403 | Plan and wallet codes | Your allowance or wallet needs attention. See Costs. |
Every code is listed in the Send a message reference, and WhatsApp's own failure codes in Best practices and FAQ.
Follow your messages
- API: Retrieve a message by its ID for its current status, cost, and content.
- Webhooks: subscribe to whatsapp.message.sent, delivered, read, and failed to be notified of each change.
- Dashboard: Messaging → Logs lists every message with its timeline and content; Messaging → Analytics shows volume and delivery rates by day.
Costs
Two parties charge for a WhatsApp message:
| Charged by | What | Where |
|---|---|---|
| Nexiom Connect | Each message you send, against your plan | Your Nexiom plan, then your Nexiom wallet |
| Meta | WhatsApp's own fee, which depends on the template category and the recipient's country | The payment method of your WhatsApp Business Account, in WhatsApp Manager |
Your plan includes a number of WhatsApp messages each billing period. Beyond them, each message is paid from your wallet at your plan's price per message:
- An included message counts once WhatsApp accepts it, even if it fails later.
- A message beyond the included ones reserves its price in your wallet, and is charged only when it is delivered or read. If it fails, or is not delivered within 24 hours, nothing is charged.
- Owners and admins are notified when 80% and 100% of the included messages are used.
Each message shows what Nexiom Connect charged for it: is_billable, cost as a decimal string such as "0.1000", and cost_currency. cost is "0.0000" for included messages and for messages that were not charged. It never includes Meta's fees.
When a send is refused for your plan or wallet, nothing is sent or charged:
| Response | Code | Returned when |
|---|---|---|
429 | included_usage_exhausted | Your plan's included messages are used up and the plan has no paid messages. |
403 | paid_usage_disabled | The included messages are used up and paid usage is turned off. |
429 | usage_spend_limit_exceeded | The message would go past your spending limit. |
429 | wallet_balance_insufficient | Your wallet cannot cover the message. Top it up. |
429 | service_not_in_plan | Your plan doesn't include WhatsApp messages. Change your plan. |
Limits
- Requests: your organization's API requests per second depend on your plan. A request past it returns
429with the codeapi_rate_limit_per_second_exceeded; wait a moment and retry with the same idempotency key. - Messaging limit: WhatsApp limits how many different customers your business portfolio can message in 24 hours. Past it, messages become
failed. See Messaging limits. - Throughput: each number sends up to its throughput, usually 80 messages per second. Spread large sends over time.
Retention
Message logs and their content are kept for 30 days. After that, the message is deleted and Retrieve a message returns 404. Daily totals in Messaging → Analytics are kept beyond 30 days. To keep a longer history, store the message ID and the statuses from webhooks in your own system.