POST /v1/messages
Send one WhatsApp message. Delivery is asynchronous: a 202 means it is queued, not delivered.
Scope: messages:send
Headers
| Header | Required | Notes |
|---|---|---|
Authorization | yes | Bearer wsk_… |
Content-Type | yes | application/json |
Idempotency-Key | yes | Any unique string per logical message (UUID recommended) |
Body
| Field | Type | Required | Notes |
|---|---|---|---|
to | string | yes | Digits with country code, 8–15 digits. + and spaces are stripped. |
category | string | yes | utility, marketing or authentication |
number_id | string | no | Send from a specific number. Defaults to the workspace's active number. |
template | object | conditional | Required for template messages |
template.name | string | yes | Template name, lowercase |
template.language | string | yes | e.g. en, en_US, hi |
template.components | array | no | Body parameters for {{1}}, {{2}} … |
text | string | conditional | Free-form text. Only inside a 24-hour service window. |
Template parameters
{
"type": "body",
"parameters": [
{ "type": "text", "text": "Ravi" },
{ "type": "text", "text": "A-102" }
]
}
Parameters map positionally to {{1}}, {{2}} … in the approved template body.
Example
curl -X POST https://api.msgpanda.com/v1/messages \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6b1f0b7e-2f2b-4a58-9b0e-9a1d2c3e4f50" \
-d '{
"to": "919811100010",
"category": "utility",
"template": {
"name": "order_update",
"language": "en",
"components": [{ "type": "body", "parameters": [
{ "type": "text", "text": "Ravi" },
{ "type": "text", "text": "A-102" }
]}]
}
}'
{ "id": "ea62acf4-2973-4fec-884a-ddab50214a48", "status": "queued" }
Responses
| Status | Body | Meaning |
|---|---|---|
202 | { id, status } | Queued for sending |
200 | { id, status, idempotent_replay: true } | You retried with a key we already saw; the original message is returned |
400 | idempotency_key_required | Missing header |
400 | validation_failed | Bad phone, unknown category, missing template |
403 | scope_not_permitted / number_not_allowed | Key lacks the scope, or is not bound to that number |
409 | number_not_active | The sending number is paused |
409 | template_not_approved | Template is not approved by Meta |
Idempotency semantics
- Scope is your workspace.
- A second request with the same
Idempotency-Keyreturns the original message and its current status. - Two different payloads with the same key return the original message — the payload is not compared.
- Keys never expire automatically; use a deterministic UUID per logical message.
What status means
| Status | Meaning |
|---|---|
queued | Accepted, waiting for the worker |
sending | In flight to Meta |
sent | Meta accepted it (a wamid exists) |
delivered | Reached the recipient's device |
read | The recipient opened it |
failed | Permanently failed; see error on the message |