Skip to content
API PlatformDevelopers
POST/v1/messages/templatestable

Send a WhatsApp template

Needs
Send WhatsApp messages; manage templates, quick messages and WhatsApp short links (messaging:write)
Plan
Any plan with API access
Limits
60 a minute per key · 20 an hour · 100 a day · 3 per contact a day
Dry run
Yes — a full preview with ?dry_run=true
Undo
Cannot be undone: the customer receives the message.

Sends one approved WhatsApp template to one contact, now. The customer receives it and it cannot be undone.

The variables are checked against the template first. Opt-outs and do-not-disturb are always respected. For bulk sends, use a broadcast in the app instead.

Try it with ?dry_run=true: every check runs and the preview tells you which number it would send from, but nothing is sent.

Try it

Body

Who receives it.

An APPROVED template.

Send from this WhatsApp channel. Default: your default WhatsApp channel.

One value per `{{n}}` in the body, in order. No new lines or tabs.

Value for a `{{1}}` in a TEXT header.

https link to the image, video or document for a media header.

One per dynamic URL button.

Protects against doing it twice if you retry: a retry with the same key gets the first answer back instead of running again.

Test mode (dry run): nothing will change
Turns test mode off for this page only. It switches back when you leave the page.

Code and response

curl -X POST 'https://mcp.wa-api.cloud/v1/messages/template?dry_run=true' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
  "contact_id": "48213",
  "template_id": "9120",
  "channel_id": "301",
  "body_variables": [
    "Jane",
    "#1001",
    "https://track.example.com/1001"
  ]
}'

The code reads your key from $API_KEY.

Body

Body fields
FieldTypeWhat it is
contact_idrequiredstring | integerWho receives it.Signed in? Pick one from your data with “My data”.
template_idrequiredstring | integerAn APPROVED template.Signed in? Pick one from your data with “My data”.
channel_idstringSend from this WhatsApp channel. Default: your default WhatsApp channel.0–20 charactersSigned in? Pick one from your data with “My data”.
body_variablesarray of stringOne value per {{n}} in the body, in order. No new lines or tabs.0–100 items · default []
header_textstringValue for a {{1}} in a TEXT header.0–60 characters
header_media_urlstringhttps link to the image, video or document for a media header.uri · 0–2000 characters
button_url_suffixesarray of objectOne per dynamic URL button.0–2 items · default []
button_indexrequiredintegerButton position (0-based, from GET /v1/templates/{template_id}).0–9
valuerequiredstringSuffix replacing {{1}} in the URL.0–2000 characters

Headers

Headers
FieldTypeWhat it is
AuthorizationrequiredheaderBearer $API_KEY — your API key.
Api-VersionheaderThe API version to use, e.g. 2026-10-01. Default: the version your key is pinned to.one of: 2026-10-01 · pattern ^\d{4}-\d{2}-\d{2}$
Idempotency-KeyrequiredheaderRequired here. Any unique string (8–128 characters), e.g. your order id plus the step. Kept 24 hours. Not needed with dry_run=true.pattern ^[A-Za-z0-9._:-]+$ · 8–128 characters

Response 202

Accepted for delivery (or the dry-run preview).

Response fields
FieldTypeWhat it is
message_idrequiredstring or null
conversation_idrequiredstring or null
statusrequiredstringqueued (accepted for delivery) or failed.
templaterequiredobject
idrequiredstring
namerequiredstring
languagerequiredstring or null
sent_atrequiredstring or null
would_send_fromobject or nullDry run only: the channel it would send from.
idrequiredstring
namerequiredstring or null
identifierrequiredstring or null
dry_runbooleantrue when this was a dry run: every check ran and nothing changed.

Errors

Errors are application/problem+json. Branch on code.

StatusCodeWhen
400invalid_input

A field is missing or has the wrong format. errors[] points at each field. Also returned when the Idempotency-Key header is missing, or was used before with a different body.

401unauthenticated

The Authorization header is missing, the key is unknown, expired or revoked.

403entitlement_required

The workspace's plan does not include API access (api_access).

403forbidden

AUTHENTICATION templates cannot be sent through the API.

403insufficient_scope

The key does not have the permission this operation needs.

404not_found

The contact, template or channel does not exist.

409conflict

The template is not APPROVED, or the channel is not connected. Also returned while a request with the same Idempotency-Key is still running.

429rate_limited

The key or workspace went over its rate limit. Wait for Retry-After seconds. A send cap was reached (reason: send_cap, with window: hour, day or contact_day).

503upstream_unavailable

A service behind the API is briefly unavailable. Safe to retry with backoff.

504timeout

The change did not finish in time. Retry with the same Idempotency-Key: it never runs twice.

Examples

Tell a customer their order shipped

Request body

{
  "contact_id": "48213",
  "template_id": "9120",
  "channel_id": "301",
  "body_variables": [
    "Jane",
    "#1001",
    "https://track.example.com/1001"
  ]
}

Response 202

{
  "message_id": null,
  "conversation_id": "77410",
  "status": "dry_run",
  "template": {
    "id": "9120",
    "name": "order_shipped",
    "language": "en_US"
  },
  "sent_at": null,
  "would_send_from": {
    "id": "301",
    "name": "Main WhatsApp",
    "identifier": "+15555550100"
  },
  "dry_run": true
}

Operation path

The same operation is also at POST /v1/ops/messaging_send_template_message, with every field in the JSON body.