// Endpoints
Messages
Send text, media and approved templates to any WhatsApp number.
/api/v1/messagesSend a message
Scope: messages:send
Pass a phone number, not an internal id. If we have never seen the number, a contact and a conversation are created for it, and the message appears in your team's inbox like any other.
Body
| Field | Type | Notes |
|---|---|---|
to | string, required | E.164, e.g. +919876543210 |
type | string | text (default), template, image, video, document or audio |
text | string | The message, or the caption for media |
media_url | string | Required for media. A public https URL |
filename | string | Optional, shown for documents |
template | object | Required when type is template — see below |
reply_to_message_id | string | Quote an earlier message in the same conversation |
name | string | Names the contact if this send creates one |
Text
curl -X POST https://whatsapp.esendblue.com/api/v1/messages \
-H "Authorization: Bearer wacrm_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{ "to": "+919876543210", "text": "Your order has shipped." }'Media
curl -X POST https://whatsapp.esendblue.com/api/v1/messages \
-H "Authorization: Bearer wacrm_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "+919876543210",
"type": "document",
"media_url": "https://files.example.com/invoice-1042.pdf",
"filename": "Invoice 1042.pdf",
"text": "Your invoice"
}'Templates#
Outside the 24-hour window — for example, the first message to a customer — only an approved template goes out. Build one under Settings → Templates, wait for Meta to approve it, then send it by name and language.
curl -X POST https://whatsapp.esendblue.com/api/v1/messages \
-H "Authorization: Bearer wacrm_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "+919876543210",
"type": "template",
"template": {
"name": "order_update",
"language": "en_US",
"params": ["A1042", "Thursday"]
}
}'| params as an object | Fills |
|---|---|
body | Body variables {{1}}, {{2}}… in order |
headerText | A text header's {{1}} |
headerMediaUrl | An image, video or document header for this send |
buttonParams | Keyed by button position: the {{1}} of a URL button, or a copy-code value |
otpCode | The code, for authentication templates — see below |
An array, as in the first example, is shorthand for { "body": [...] }.
Authentication (OTP) templates
Meta writes the text of an authentication template; you supply only the code. Pass it as otpCode and it fills both the message and the copy-code or autofill button. If you want the code generated, stored safely and checked for you, use one-time passwords instead.
curl -X POST https://whatsapp.esendblue.com/api/v1/messages \
-H "Authorization: Bearer wacrm_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "+919876543210",
"type": "template",
"template": { "name": "login_code", "language": "en", "params": { "otpCode": "483920" } }
}'Response#
{
"data": {
"message_id": "…",
"whatsapp_message_id": "wamid.HBgM…",
"conversation_id": "…",
"contact_id": "…",
"contact_created": false
}
}A 201 means Meta accepted the message, not that it was delivered. To track delivery, subscribe to message.status_updated or read the conversation's messages.
Errors#
If the send was refused before reaching Meta, nothing was delivered and nothing was charged. These codes come on top of the common ones:
| code | Status | What to do |
|---|---|---|
window_closed | 409 | Outside the 24-hour window. Send an approved template |
template_not_approved | 422 | The template is pending, rejected or paused at Meta |
template_not_synced | 422 | The template is not in your list yet. Run "Sync from Meta" under Settings → Templates |
contact_opted_out | 403 | Marketing template to a contact who replied STOP. Never sent, by design |
frequency_capped | 429 | Marketing template to someone who has reached your project's frequency cap |
daily_budget_exceeded | 429 | Today's marketing allowance for your number is used up. Try tomorrow |
policy_lookup_failed | 503 | We could not check the rules above, so nothing was sent. Safe to retry |
whatsapp_not_configured | 400 | No WhatsApp number is connected to this project yet |
plan_locked | 402 | The project's plan has ended. Sending resumes once it is renewed |
per_user_marketing_cap | 429 | Meta limits how many marketing messages one person gets. Try another day |
undeliverable_recipient | 422 | The number is not on WhatsApp, or cannot receive this message |
marketing_excluded | 422 | Meta does not deliver marketing to this recipient |
spam_rate_limit | 429 | Meta has slowed this number down. Stop and send less |
rate_limited | 429 | Temporary. Retry with backoff — the only retryable Meta error |
meta_error | 502 | Meta rejected the send for another reason; the message says which |
Marketing templates follow campaign rules
Don't retry everything
rate_limited, policy_lookup_failed and internal are worth retrying automatically. Retrying a quality or policy refusal can get your number restricted by Meta.