Skip to content

WhatsApp templates

Outside the 24-hour window WhatsApp only accepts templates: pre-approved message shapes with variable slots. Meta reviews each one.

Terminal window
curl -X POST https://api.neike.dev/api/templates/submit \
-H "x-neike-api-key: whk_live_…" \
-H "Content-Type: application/json" \
-d '{
"channel_id": "nk_dvzobn7m4h4y",
"template_data": {
"name": "order_ready",
"language": "en",
"category": "UTILITY",
"components": [
{ "type": "BODY", "text": "Hi {{1}}, your order {{2}} is ready." }
]
}
}'

template_data is passed through to Meta in their own format, so anything their template API accepts works here. Names are lowercase with underscores, and category is UTILITY, MARKETING or AUTHENTICATION — picking the wrong one is the most common rejection reason.

Approval is asynchronous. Status changes arrive on your webhook, and templates are listed with their current state:

Terminal window
curl -X POST https://api.neike.dev/api/templates/list \
-H "x-neike-api-key: whk_live_…" \
-H "Content-Type: application/json" \
-d '{ "channel_id": "nk_dvzobn7m4h4y" }'

The submit response includes category when Meta reports it. Each listed template carries rejected_reason, quality_score, previous_category and correct_category when Meta provides them, passed through as Meta returns them. If the channel has no WABA attached, submit, list and edit answer with externalCode: 400 and a clear message.

Edit a template that already exists in the channel’s WABA. Meta replaces the whole components array, and the template goes back to review.

Terminal window
curl -X POST https://api.neike.dev/api/templates/edit \
-H "x-neike-api-key: whk_live_…" \
-H "Content-Type: application/json" \
-d '{
"channel_id": "nk_dvzobn7m4h4y",
"template_name": "order_ready",
"language": "en",
"components": [
{ "type": "BODY", "text": "Hi {{1}}, order {{2}} is ready for pickup." }
]
}'

Send at least one of components, category or message_send_ttl_seconds. Only APPROVED, REJECTED and PAUSED templates can be edited, and the category of an APPROVED template cannot change.

The template is found by template_name inside the channel’s WABA. language is required when the name exists in several languages, and it is the full code that list returns (for example es_AR), not the base code the webhook carries. template_id is optional and only confirms the match: it must be the id of the template found by name.

{
"externalCode": 200,
"success": true,
"id_external": "123456789",
"name": "order_ready",
"language": "en",
"status": "PENDING"
}

Errors keep HTTP 200 and arrive in externalCode and message: 404 when the template is not in the WABA, 400 when language is missing or template_id does not match.

Templates belong to the WABA, not to the channel. With an API key (or MCP grant) limited to specific channels, editing requires the scope to cover ALL the workspace channels that share that WABA. If any is missing, the call returns the same 404 “Canal no encontrado” as a channel that does not exist. Creating, listing and media-handle only require the channel. Keys without a channel restriction and the console are unaffected.

A template with an image, video or PDF header needs a media handle in example.header_handle. Get it from a public URL:

Terminal window
curl -X POST https://api.neike.dev/api/templates/media-handle \
-H "x-neike-api-key: whk_live_…" \
-H "Content-Type: application/json" \
-d '{ "channel_id": "nk_dvzobn7m4h4y", "url": "https://cdn.example.com/banner.jpg" }'
{ "externalCode": 200, "header_handle": "4::aW1h…" }

The file must be image/jpeg, image/png, video/mp4 or application/pdf, up to 16 MB. Anything else answers externalCode: 400.

Template status changes are sent to POST {your_url}/chat/updateTemplateStatus with the x-webhook-token header (when you configured a secret), like every other webhook.

{
"metadata": {
"event_type": "message_template_status_update",
"template_id": "1848392017465",
"template_name": "order_ready",
"language": "en",
"status": "APPROVED",
"reason": null,
"category": "UTILITY",
"waba_id": "102290129340398",
"channel_ids": ["nk_dvzobn7m4h4y"]
}
}

waba_id is the WABA that owns the template. channel_ids lists your active channels on that WABA, so you know which one to use to edit or resubmit it. category is present only when Meta sends it, and language is normalized to the base code (es_AR arrives as es).

Sending a template needs your WABA’s message template namespace. You get it when the channel is connected — it is part of the payload the embedded signup returns, and it is shown on the channel in the console.

Terminal window
curl -X DELETE https://api.neike.dev/api/templates/delete \
-H "x-neike-api-key: whk_live_…" \
-H "Content-Type: application/json" \
-d '{ "channel_id": "nk_dvzobn7m4h4y", "template_name": "order_ready" }'

Deleting is permanent on Meta’s side and the name cannot be reused immediately.

Templates belong to the WABA, not to the channel. With an API key (or MCP grant) limited to specific channels, deleting requires the scope to cover ALL the workspace channels that share that WABA. If any is missing, the call returns the same 404 “Canal no encontrado” as a channel that does not exist. Creating, listing and media-handle only require the channel. Keys without a channel restriction and the console are unaffected.