Sending WhatsApp messages
Prerequisites
- A WhatsApp template synced in the dashboard. Template management is dashboard-only — the API references templates by
template_id. - An API key with the
whatsapp:sendscope.
Payload
POST /v1/whatsapp/messages takes one item; one message row is created per recipient:
| Field | Type | Required | Notes |
|---|---|---|---|
template_id | uuid | yes | Dashboard-managed template |
to | string | yes | E.164 ^\+[1-9]\d{7,14}$; separators stripped; 1–50 numbers = one row each |
parameters | mapstringstring | if the template declares slots | Keys are scope:position (e.g. body:1, plus url:1 when the template has a dynamic URL button); every declared slot required, unknown keys rejected |
idempotency_key | string | no | Replay returns 200 + original |
Request:
const messages = await notideus.whatsapp.messages.send({
template_id: '9f2c7a1e-0b1c-4d3e-8a5f-2e6b9c4d7a01',
to: ['+33612345678'],
parameters: {
'body:1': 'Jane'
},
idempotency_key: 'order-12345-whatsapp'
});
// one entry per recipient: [{ id, to, status }]
$messages = $notideus->whatsapp()->messages()->send([
'template_id' => '9f2c7a1e-0b1c-4d3e-8a5f-2e6b9c4d7a01',
'to' => ['+33612345678'],
'parameters' => [
'body:1' => 'Jane'
],
'idempotency_key' => 'order-12345-whatsapp'
]);
// one entry per recipient: [{ id, to, status }]
{
"template_id": "9f2c7a1e-0b1c-4d3e-8a5f-2e6b9c4d7a01",
"to": ["+33612345678"],
"parameters": {
"body:1": "Jane"
},
"idempotency_key": "order-12345-whatsapp"
}
A real response (201 Created) — one row per recipient:
{
"data": [
{
"id": "01a0a11d-a584-72f2-b86d-dad8e76b2c97",
"to": "+33612345678",
"status": "queued"
}
]
}
With three recipients, data holds three such rows.
Parameters
Templates declare slots and you fill them by scope:position keys — body:1, header:1, and so on, plus url:1 when the template has a dynamic URL button:
- Every declared slot must be present and non-empty.
- Unknown keys are rejected.
Violations come back as 400 invalid_params, e.g. invalid parameters: missing body:2 or invalid parameters: unknown foo:1.
Phone numbers
Numbers must be E.164 — ^\+[1-9]\d{7,14}$. Cosmetic separators (spaces, dashes, dots, parentheses) are stripped before validation, so +33 6 12 34 56 78 is accepted as +33612345678. Each item takes 1–50 recipients, and every recipient gets its own row and status — 50 phones in, 50 rows out.
Batches and failures
POST /v1/whatsapp/messages/batch mirrors the email batch: up to 100 items in a messages array, one indexed result per item. Anything unparseable fails just that item — this is a real response for a batch whose first item had a bad phone:
{
"data": [
{
"index": 0,
"error": {
"code": "invalid_phone",
"message": "to[0]: invalid_phone: +abc is not a valid E.164 phone number (e.g. +33612345678)"
}
}
]
}
Decode: index is the item's position in the messages array, and to[0] marks which entry in that item's to array was rejected. (The doubled invalid_phone label in the message is verbatim from the API.)
With the SDK: await notideus.whatsapp.messages.sendBatch([...]) takes the
same item shape and resolves with the same indexed per-item results —
batch item failures are returned, not thrown, exactly like email batches.
In PHP: $notideus->whatsapp()->messages()->sendBatch([...]) takes the same
item shape and returns the same indexed per-item results — batch item
failures are returned, not thrown, exactly like email batches.
Statuses
Each row moves queued → sent → delivered → read (or failed) as Meta reports back via webhook.