Creating & managing templates
Creating & managing templates
Templates are the pre-approved messages that let you start a conversation and message a customer outside the 24-hour window. This page covers creating templates (utility, marketing, media-header and authentication/OTP), uploading header sample media, editing, listing and the validation rules — over the app-key API.
Everything here needs the whatsapp:templates scope on your app key. Sending an already-approved template is a separate call (/send-template) covered in the WhatsApp API.
Before you start
Get an app key that holds whatsapp:templates (see Scopes) and find your channel id — every template lives on one native channel (your connected WhatsApp number):
curl https://api.telenow.ai/api/app-whatsapp/channels \
-H "Authorization: Bearer vai_app_…"
{ "success": true, "data": { "channels": [
{ "id": "b1e2c3d4-…", "label": "Acme Support", "phone": "+14155550100" }
] } }
Use that id as the {id} in the paths below. Send the key on every request:
Authorization: Bearer vai_app_xxxxxxxxxxxxxxxx
Templates work on native channels only (numbers connected through the “WhatsApp Business, set up for you” path). The dashboard equivalent is
POST /api/orgs/{orgId}/whatsapp-cloud/channels/{cid}/templateswith a user JWT.
Create a utility template
POST /channels/{id}/templates submits a template definition to Meta for review and mirrors it locally as PENDING. The body is camelCase:
| Field | Type | Notes |
|---|---|---|
name | string | Required. Lowercase letters, digits and _ only, ≤ 512 chars. Immutable after creation |
language | string | Required. BCP-47 tag, e.g. en_US, en_GB, hi |
category | string | Required. One of MARKETING, UTILITY, AUTHENTICATION |
components | array | Required. Meta’s definition array (see below). Each {{n}} needs an example |
allowCategoryChange | bool | Optional. Let Meta re-categorise (e.g. UTILITY → MARKETING) instead of rejecting |
A utility template with two body variables — note the example.body_text (a list of one row of sample values, one per {{n}}):
curl -X POST https://api.telenow.ai/api/app-whatsapp/channels/b1e2c3d4-…/templates \
-H "Authorization: Bearer vai_app_…" \
-H "Content-Type: application/json" \
-d '{
"name": "order_update",
"language": "en_US",
"category": "UTILITY",
"components": [
{ "type": "BODY",
"text": "Hi {{1}}, your order {{2}} has shipped and will arrive soon.",
"example": { "body_text": [["Asha", "#1234"]] } }
]
}'
{ "success": true, "data": { "template": {
"id": "7c1a9f22-…",
"name": "order_update",
"language": "en_US",
"category": "UTILITY",
"status": "PENDING",
"qualityScore": null,
"rejectedReason": null,
"metaTemplateId": "1443219876543210"
} } }
The template is now PENDING at Meta. See Approval & status for how it becomes APPROVED.
Create a marketing template
A richer template: a text header with a variable, a body, a footer, and a full set of buttons — a dynamic URL, a QUICK_REPLY, a PHONE_NUMBER, and a COPY_CODE coupon. Every variable ({{n}}) in the header, body and URL carries its own example:
curl -X POST https://api.telenow.ai/api/app-whatsapp/channels/b1e2c3d4-…/templates \
-H "Authorization: Bearer vai_app_…" \
-H "Content-Type: application/json" \
-d '{
"name": "spring_sale",
"language": "en_US",
"category": "MARKETING",
"components": [
{ "type": "HEADER", "format": "TEXT", "text": "{{1}} sale is live",
"example": { "header_text": ["Summer"] } },
{ "type": "BODY",
"text": "Hi {{1}}, enjoy 20% off everything this week. Use your code at checkout.",
"example": { "body_text": [["Asha"]] } },
{ "type": "FOOTER", "text": "Reply STOP to opt out" },
{ "type": "BUTTONS", "buttons": [
{ "type": "URL", "text": "Shop now", "url": "https://shop.example.com/{{1}}",
"example": ["spring"] },
{ "type": "QUICK_REPLY", "text": "No thanks" },
{ "type": "PHONE_NUMBER", "text": "Call us", "phone_number": "+14155550100" },
{ "type": "COPY_CODE", "example": "SAVE20" }
] }
]
}'
{ "success": true, "data": { "template": {
"id": "9d33be07-…",
"name": "spring_sale",
"language": "en_US",
"category": "MARKETING",
"status": "PENDING",
"qualityScore": null,
"rejectedReason": null,
"metaTemplateId": "1443219876500000"
} } }
Button notes:
URL— static, or dynamic with a trailing{{1}}in theurlplus anexamplevalue for the variable part.QUICK_REPLY— a tappable reply; the tap arrives back to you as abuttonreply on thewhatsapp.message.receivedwebhook.PHONE_NUMBER— dialsphone_number(E.164).COPY_CODE— a coupon button;exampleis a sample code. (This is the marketing coupon copy-code button — different from the OTP copy-code used in authentication templates.)
Create a media-header template
For an image / video / document header, Meta needs a sample asset at definition time. That’s a two-step flow: upload the sample to get a handle, then reference it in the header’s example.header_handle.
Step 1 — upload the sample asset. POST /channels/{id}/templates/media takes a multipart/form-data body with a single file field. Accepts image/jpeg, image/png, video/mp4, application/pdf, up to 16 MB:
curl -X POST https://api.telenow.ai/api/app-whatsapp/channels/b1e2c3d4-…/templates/media \
-H "Authorization: Bearer vai_app_…" \
-F "[email protected]"
{ "success": true, "data": { "handle": "4::aW1hZ2UvcG5n:ARZ…" } }
Step 2 — create the template with the returned handle in an IMAGE (or VIDEO / DOCUMENT) header:
curl -X POST https://api.telenow.ai/api/app-whatsapp/channels/b1e2c3d4-…/templates \
-H "Authorization: Bearer vai_app_…" \
-H "Content-Type: application/json" \
-d '{
"name": "receipt_with_logo",
"language": "en_US",
"category": "UTILITY",
"components": [
{ "type": "HEADER", "format": "IMAGE",
"example": { "header_handle": ["4::aW1hZ2UvcG5n:ARZ…"] } },
{ "type": "BODY",
"text": "Hi {{1}}, here is your receipt for order {{2}}.",
"example": { "body_text": [["Asha", "#1234"]] } },
{ "type": "FOOTER", "text": "Thanks for shopping with Acme" }
]
}'
{ "success": true, "data": { "template": {
"id": "2a71c0e5-…", "name": "receipt_with_logo", "language": "en_US",
"category": "UTILITY", "status": "PENDING",
"qualityScore": null, "rejectedReason": null, "metaTemplateId": "1443219876511111"
} } }
Use "format": "VIDEO" (upload an mp4) or "format": "DOCUMENT" (upload a pdf) for those header types. When you later send this template you supply the runtime asset — the mediaId from a normal media upload — in the header parameter; see Templates with a media header.
Create an authentication (OTP) template
Authentication templates use a fixed shape — you don’t write the body text. Meta generates the OTP wording; you only toggle the security recommendation, set the code expiry, and add the copy-code button. A custom body would be rejected.
curl -X POST https://api.telenow.ai/api/app-whatsapp/channels/b1e2c3d4-…/templates \
-H "Authorization: Bearer vai_app_…" \
-H "Content-Type: application/json" \
-d '{
"name": "login_code",
"language": "en_US",
"category": "AUTHENTICATION",
"components": [
{ "type": "BODY", "add_security_recommendation": true },
{ "type": "FOOTER", "code_expiration_minutes": 5 },
{ "type": "BUTTONS", "buttons": [
{ "type": "OTP", "otp_type": "COPY_CODE", "text": "Copy code" }
] }
]
}'
{ "success": true, "data": { "template": {
"id": "5f0d84a1-…", "name": "login_code", "language": "en_US",
"category": "AUTHENTICATION", "status": "PENDING",
"qualityScore": null, "rejectedReason": null, "metaTemplateId": "1443219876522222"
} } }
add_security_recommendation: trueappends Meta’s “For your security, don’t share this code” line.code_expiration_minutes(1–90) shows the customer when the code expires and greys out the copy button afterwards.- Only
COPY_CODEOTP buttons are supported today. One-tap autofill needs Android app-signing details that aren’t collected yet, so it’s not available.
When you send this template, the code is your first variables value (e.g. "variables": ["482913"]), which fills both the body and the copy-code button.
Approval & status
Every create returns status: "PENDING". Meta review usually lands within minutes and moves the template to APPROVED, REJECTED, or PAUSED. How to observe it:
- Poll the list endpoint.
GET /channels/{id}/templatesreturns only approved templates — once your template appears there, it’s live. - No webhook for template status. There is no webhook for template status changes —
whatsapp.account.healthfires only for account/number quality and review outcomes (account_update,phone_number_quality_update,account_review_update), never for template approval/rejection/pause. BecauseGET /channels/{id}/templatesreturns only APPROVED templates, aREJECTEDorPAUSEDtransition can’t be observed via the API — check the dashboard’s Message templates view for those.
The create/edit response echoes the live status, plus qualityScore (Meta’s green/yellow/red rating, null until it has traffic) and rejectedReason (populated when status is REJECTED, e.g. a policy or formatting reason).
Validation limits
The server validates before forwarding to Meta, so malformed templates fail with a clear 400 instead of an opaque Meta rejection:
| Rule | Limit |
|---|---|
name | Lowercase letters, digits, _; ≤ 512 chars |
category | MARKETING | UTILITY | AUTHENTICATION |
| BODY | Exactly one non-empty BODY (marketing/utility); text ≤ 1024 |
| HEADER text | ≤ 60 chars |
| FOOTER text | ≤ 60 chars |
| Button text | ≤ 25 chars each |
| Buttons | ≤ 10 total |
| Variables | Contiguous {{1}}…{{N}} (no gaps); each needs an example |
Authentication templates skip the BODY-text rule (the shape is fixed). A missing or non-contiguous variable, an over-long field, or a missing example all return 400 with the offending field named.
Edit a template
PATCH /templates/{templateId} updates an existing template’s components (and optionally category). name and language are immutable at Meta — you can’t change them; create a new template instead. Editing re-submits for review, so the template returns to PENDING.
curl -X PATCH https://api.telenow.ai/api/app-whatsapp/templates/7c1a9f22-… \
-H "Authorization: Bearer vai_app_…" \
-H "Content-Type: application/json" \
-d '{
"components": [
{ "type": "BODY",
"text": "Hi {{1}}, your order {{2}} is out for delivery today.",
"example": { "body_text": [["Asha", "#1234"]] } }
]
}'
{ "success": true, "data": { "template": {
"id": "7c1a9f22-…", "name": "order_update", "language": "en_US",
"category": "UTILITY", "status": "PENDING",
"qualityScore": null, "rejectedReason": null, "metaTemplateId": "1443219876543210"
} } }
Approved, rejected and paused templates can all be edited in place. The same validation limits apply.
List approved templates
GET /channels/{id}/templates returns the channel’s approved templates and, for each, how many variables it takes — everything you need to call /send-template:
curl https://api.telenow.ai/api/app-whatsapp/channels/b1e2c3d4-…/templates \
-H "Authorization: Bearer vai_app_…"
{ "success": true, "data": { "templates": [
{ "name": "order_update", "language": "en_US", "category": "UTILITY",
"variables": [ { "index": 1 }, { "index": 2 } ] },
{ "name": "login_code", "language": "en_US", "category": "AUTHENTICATION",
"variables": [ { "index": 1 } ] }
] } }
variables lists the body placeholders in order — pass one variables string per index when you send. Templates still PENDING or REJECTED are not returned here; use the dashboard Message templates view to see those.
Endpoint reference
Base /api/app-whatsapp, native channels only. Rate-limited ~20 req/s (burst 60); template-media upload capped at 16 MB.
| Method | Path | Scope | Purpose |
|---|---|---|---|
GET | /channels/{id}/templates | whatsapp | List approved templates |
POST | /channels/{id}/templates | whatsapp:templates | Create a template → { template } |
POST | /channels/{id}/templates/media | whatsapp:templates | Upload a header sample → { handle } |
PATCH | /templates/{tid} | whatsapp:templates | Edit a template → { template } |
Not built yet: carousel and limited-time-offer template types, and one-tap OTP.
Related
- WhatsApp API — the full REST surface, including sending an approved template.
- Broadcasts — send an approved template to many contacts.
- Reading conversations · Typing indicators & read receipts.
- Webhooks & events — inbound messages, statuses and account-health.
- Scopes, permissions & security · WhatsApp product guide.