POST /v1/{phone_number_id}/messages
Sends one WhatsApp message to one recipient. The request body is the WhatsApp Cloud API
message body, unchanged: if you already integrate directly with
https://graph.facebook.com/<version>/<PHONE_NUMBER_ID>/messages, you can switch to Hello by
changing only the base URL and the credential.
POST /v1/{phone_number_id}/messagesAuthorization: Bearer hello_live_xxxxxxxxxxxxxxxxContent-Type: application/jsonphone_number_id is the sending number’s numeric WhatsApp phone number ID, shown on the
Accounts page of the dashboard.
Query parameters
Section titled “Query parameters”| Parameter | Type | Default | Description |
|---|---|---|---|
sync |
boolean | false |
Wait up to 10 seconds for WhatsApp to accept the message. See queued and synchronous sends. |
Headers
Section titled “Headers”| Header | Required | Description |
|---|---|---|
Idempotency-Key |
No | A retry with the same key returns the original message instead of sending again. |
Request body
Section titled “Request body”| Field | Type | Required | Description |
|---|---|---|---|
messaging_product |
string | No | Defaults to and must be whatsapp |
recipient_type |
string | No | Defaults to individual. group is rejected: Hello doesn’t support the Groups API. |
to |
string | Yes | Recipient’s number, with a leading + and country code, for example +16505551234. See phone numbers. |
type |
string | Yes | text, template, image, video, audio, document, sticker, location, contacts, interactive or reaction |
context |
object | No | { "message_id": "<wamid>" } to send as a reply, quoting an earlier message |
biz_opaque_callback_data |
string | No | Passed through unchanged; returned on delivery webhooks |
<type> |
object or array | Yes, matching type |
The type-specific object. contacts is an array; every other type is an object. |
Every field is forwarded to WhatsApp as sent — Hello validates only what it needs to route, window-check and bill the message, so new Cloud API fields work without a Hello release.
Supported message types
Section titled “Supported message types”Each type has its own page with full examples and fields:
- Template — the only type that can start a conversation
- Text
- Image, video, audio, document, sticker
- Location, contacts
- Interactive — reply buttons, lists, call-to-action URL, location request, address and flow
- Reaction
- Reply to a message using
context - Mark a message as read — a variant of this same endpoint
Example
Section titled “Example”{ "to": "+919999999999", "type": "text", "text": { "body": "Your order has shipped." }}curl -X POST "$HELLO_API_URL/v1/<your-phone-number-id>/messages" \ -H "Authorization: Bearer $HELLO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "to": "+919999999999", "type": "text", "text": { "body": "Your order has shipped." } }'Responses
Section titled “Responses”Every type, including template, is accepted onto Hello’s send queue and dispatched fairly
across your organization’s traffic; every type except template also needs an open 24-hour
window at the time it’s accepted.
202 Accepted: queued (default)
Section titled “202 Accepted: queued (default)”{ "messaging_product": "whatsapp", "contacts": [{ "input": "+919999999999", "wa_id": "919999999999" }], "messages": [{ "id": "b7e1d2c3-4f5a-4b6c-8d9e-0a1b2c3d4e5f", "message_status": "queued" }]}| Field | Type | Description |
|---|---|---|
contacts[0].input |
string | to exactly as you sent it |
contacts[0].wa_id |
string | Recipient’s number, normalized digits, no + |
messages[0].id |
string (UUID) | Hello’s message ID. Use it with GET /v1/messages/{id}. It isn’t WhatsApp’s wamid — the send is still queued when this responds. |
messages[0].message_status |
string | queued |
200 OK: sent (?sync=true)
Section titled “200 OK: sent (?sync=true)”Returned when ?sync=true and the send reaches WhatsApp within the 10-second wait.
messages[0].message_status is sent and messages[0].wamid carries WhatsApp’s own message ID.
Without ?sync=true, or if the queue hasn’t dispatched it within 10 seconds, you get 202 as
above — poll GET /v1/messages/{id} either way.
Mark as read
Section titled “Mark as read”A body of {"messaging_product": "whatsapp", "status": "read", "message_id": "<wamid>"} (no
type) is a mark-as-read / typing-indicator request. It’s sent to WhatsApp directly, not queued,
and returns 200 {"success": true}. See Mark a message as read.
Errors
Section titled “Errors”| Status | code |
Cause |
|---|---|---|
400 |
VALIDATION_ERROR |
Invalid JSON, missing to/type, an unsupported type, the wrong shape for the type’s object, a group recipient, or to missing the + and country code |
400 |
TEMPLATE_LANGUAGE_REQUIRED |
(type: template) The name exists in several languages and none was given — available_languages lists them |
401 |
UNAUTHORIZED |
Missing, wrong or disabled API key |
403 |
OUTSIDE_24H_WINDOW |
A non-template message to a recipient whose 24-hour window is closed |
403 |
TEMPLATE_NOT_APPROVED |
The template exists but isn’t APPROVED |
403 |
PLAN_* |
Your plan doesn’t allow this send. See plan errors. |
404 |
ACCOUNT_NOT_FOUND |
No number with this phone_number_id in your organization |
404 |
TEMPLATE_NOT_FOUND |
(type: template) No template with this name on the number’s WABA |
404 |
TEMPLATE_LANGUAGE_NOT_FOUND |
(type: template) The name exists but not in the requested language |
409 |
ACCOUNT_NOT_REGISTERED |
The number hasn’t completed phone registration |
422 |
SEND_FAILED |
WhatsApp rejected the message. Only returned with ?sync=true or for mark-as-read — otherwise a rejection lands on the message as status: "failed". |
{ "message": "template \"order_shipped\" has no \"fr\" language; available: en, en_US, hi", "code": "TEMPLATE_LANGUAGE_NOT_FOUND", "available_languages": ["en", "en_US", "hi"]}