Messaging
Send freeform text and approved WhatsApp template messages.
The Developer API exposes two send operations. Both return one result per recipient and process accepted results asynchronously.
Prerequisites
- Portal reports the WABA connection as ready.
- API key has WABA access.
- Connected account has a selected phone number.
- Template sends use an approved template.
- Text sends target a recipient with an open 24-hour customer-service window.
The backend resolves the owned WABA and selected phone number. Do not send business, WABA, or phone-number IDs.
Authentication
Authorization: Bearer ntfy_your_api_key
Content-Type: application/jsonSend Text
POST /v1/waba-api/messages/text
Use this operation only after the customer has sent a qualifying inbound message during the preceding 24 hours.
curl --request POST \
"$NOTIFY_API_BASE_URL/v1/waba-api/messages/text" \
--header "Authorization: Bearer $NOTIFY_WABA_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"to": ["255700000001", "255700000002"],
"text": "Your support request has been updated.",
"idempotency_key": "support-update-1001"
}'Request Body
| Field | Required | Validation |
|---|---|---|
to | yes | String or string array; 1-500 entries after trimming/deduplication |
text | yes | String, 1-4096 characters |
idempotency_key | no | String, maximum 128 characters |
Success
HTTP 200:
{
"status": 200,
"message": "Success",
"timestamp": "2026-09-09T10:30:00.000Z",
"path": "/v1/waba-api/messages/text",
"data": {
"results": [
{
"to": "255700000001",
"success": true,
"messageId": "d9b54c80-8a63-4f8b-83c8-71f2439f5429",
"status": "processing",
"error": null
},
{
"to": "255700000002",
"success": false,
"error": "Freeform text is not currently permitted for this recipient. Use an approved template send instead."
}
]
}
}HTTP 200 can contain both successful and failed recipient results. Inspect every entry.
Errors
- Invalid top-level DTO or more than 500 recipients: request-level
400. - Closed, unknown, or expired service window: per-recipient
success: false. - Missing selected phone number: per-recipient failure.
- Invalid API key: request-level
401. - Queue/dependency failure: request-level error or per-recipient failure, depending on where processing stops.
Related: subscribe to message.received to know when a customer opens a conversation, then process message.status.updated after sending.
Next: store each messageId and wait for status events.
Send Template Message
POST /v1/waba-api/messages/template
Template messages can start a conversation and can be sent outside the customer-service window.
curl --request POST \
"$NOTIFY_API_BASE_URL/v1/waba-api/messages/template" \
--header "Authorization: Bearer $NOTIFY_WABA_API_KEY" \
--header "Content-Type: application/json" \
--data '{
"to": "255700000001",
"template_name": "order_update",
"language": "en",
"template_parameters": {
"body": {
"1": "Jane",
"2": "ORD-1001"
}
},
"idempotency_key": "order-1001-update"
}'Request Body
| Field | Required | Validation |
|---|---|---|
to | yes | String or string array; 1-500 entries after trimming/deduplication |
template_name | yes | Non-empty approved external WhatsApp template name |
template_parameters | no | Object; flat body bindings or grouped components |
language | no | String; defaults to en |
idempotency_key | no | String, maximum 128 characters |
Success
HTTP 200 with the same envelope and per-recipient result shape as text sends:
{
"status": 200,
"message": "Success",
"timestamp": "2026-09-09T10:31:00.000Z",
"path": "/v1/waba-api/messages/template",
"data": {
"results": [
{
"to": "255700000001",
"success": true,
"messageId": "fe245daf-b447-4de7-9af4-35a2ab6cdd71",
"status": "processing",
"error": null
}
]
}
}Provider approval and parameter compatibility are checked during asynchronous worker preflight. A queued result can later become failed.
Errors
- Invalid top-level DTO or recipient list: request-level
400. - Missing selected phone number: per-recipient failure.
- Missing, unapproved, mismatched-language, or incompatible template: asynchronous
failedstatus can follow initial queue acceptance. - Invalid provider parameters: asynchronous provider failure.
- Invalid API key: request-level
401.
Related: use the template status endpoint before sending and subscribe to template.status.updated and message.status.updated.
Next: correlate status callbacks using messageId or wamid.
Recipient Processing
The to field accepts a single string or an array.
The API:
- trims each string.
- removes empty and non-string values.
- removes exact duplicate strings.
- preserves the remaining order.
- rejects an empty result.
- rejects more than 500 recipients.
Phone numbers are not normalized or validated as E.164 by this facade. Use one consistent international format without spaces or punctuation. 255700000001 and +255700000001 are different recipient strings.
Idempotency
With a client key, the API derives one key per recipient:
<idempotency_key>:<recipient>Use one stable value for retries of the same logical send. Use a new value for a new send.
When omitted, the API creates a random key per recipient. Retries without a stable client key are not deduplicated as the same logical request.
Template Parameters
Flat body values
{
"template_parameters": {
"1": "Jane",
"2": "ORD-1001"
}
}Grouped values
{
"template_parameters": {
"header": {
"1": "Acme"
},
"body": {
"1": "Jane",
"2": "ORD-1001"
},
"buttons": [
{
"sub_type": "quick_reply",
"parameters": [
{
"type": "payload",
"payload": "order_1001"
}
]
}
]
}
}- Flat objects become body parameters.
- Body/header scalar values become text parameters.
- Numeric keys sort numerically; remaining keys sort alphabetically.
- Button entries are forwarded as button components.
- The Developer API mapper does not convert media URLs into media-header parameters.
Match the approved template's exact component and variable order.
Result Fields
| Field | Presence | Meaning |
|---|---|---|
to | always | Normalized recipient string used for this result |
success | always | Whether this recipient entered the accepted send path |
messageId | successful result when persisted | Notify Africa message identifier |
wamid | optional | Meta message identifier, often unavailable in the initial response |
status | optional | Current local processing state |
error | optional | Recipient-specific rejection or processing reason |
Message Lifecycle
Any processing stage can become failed. Provider values that are not mapped can appear as unknown.
There is no API-key message read/status operation. Use signed webhook events for asynchronous state changes and the Portal for operational inspection.
