Sending template messages

Templates are the only way to start a conversation or message a customer outside the 24-hour window — Meta requires a pre-approved template for any business-initiated message. You send one through POST /send-template, naming an approved template and filling its variables in order.

This page covers listing your approved templates and sending them. To author and submit templates for approval, see Creating templates; to send one template to many recipients, see Broadcasts.

With an app key, templates send on Meta Cloud numbers (native or your own Meta app). With your org API key they also send on partner numbers (GupShup, Plivo, AiSensy); WhatsApp Web numbers can't send templates.


List approved templates

Before sending, fetch the approved templates on a channel so you know each template's exact name, language, and how many variables it expects. GET /channels/{id}/templates returns approved templates only, each with a variables array whose entries carry the positional index of every {{n}} placeholder in the template's body (header and button values go in components).

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": "welcome",
        "language": "en_US",
        "category": "MARKETING",
        "variables": []
      }
    ]
  }
}

The length of variables is how many strings you must pass — order_update above takes two, welcome takes none.

Send a template

POST /send-template takes { channelId, to, template, language?, variables }. language defaults to en_US; variables is a positional array of strings that fill {{1}}, {{2}}, … in order. A successful send returns the wamid, just like /send.

curl -X POST https://api.telenow.ai/api/app-whatsapp/send-template \
  -H "Authorization: Bearer vai_app_…" \
  -H "Content-Type: application/json" \
  -d '{
    "channelId": "b1e2c3d4-…",
    "to": "+919876543210",
    "template": "order_update",
    "language": "en_US",
    "variables": ["Asha", "#1234"]
  }'
{ "success": true, "data": { "wamid": "wamid.HBgLMTQxNTU1MDEwMBUCABIYFj…" } }
FieldNotes
channelIdThe channel id from GET /channels
toRecipient in E.164, e.g. +919876543210
templateApproved template name
languageOptional; defaults to en_US. Must match the template's language
variablesPositional strings that fill {{1}}, {{2}}, … in order — supply exactly as many as the template's variables array

A template problem — the wrong number of values, a name or language WhatsApp doesn't have, a template Meta has paused — comes back with a plain-English explanation: as 400 when you sent components, or as 502 (whatsapp template send failed (400 Bad Request): …) when you sent variables. Meta's code number isn't in the response. Values in the wrong order can't be detected — check them against the template.

A number that replied STOP to you is blocked: 502 … has opted out of messages from this business (they replied STOP). See opt-outs.

One-time-passcode (authentication) templates

An AUTHENTICATION template carries its code twice: in the message body ({{1}}) and on its Copy code button. On a Meta Cloud number, send the code once, as the only entry in variables. Telenow reads the template's definition and puts the same code on the button for you:

curl -X POST https://api.telenow.ai/api/app-whatsapp/send-template \
  -H "Authorization: Bearer vai_app_…" \
  -H "Content-Type: application/json" \
  -d '{
    "channelId": "b1e2c3d4-…",
    "to": "+919876543210",
    "template": "login_code",
    "language": "en_US",
    "variables": ["482913"]
  }'
  • Telenow reads the template from its synced copy of the number's templates. A template made outside Telenow is picked up within a few hours, or as soon as you open that number's Templates page. Until then, the send fails with WhatsApp's message that the button needs a parameter.
  • If you send components instead, they go to WhatsApp exactly as written. Include both parts yourself: the body parameter, and { "type": "button", "sub_type": "url", "index": "0", "parameters": [ { "type": "text", "text": "482913" } ] } with the button's position as index.
  • On a partner number (Plivo, GupShup, AiSensy) nothing fills the button for you. Send it through a Meta Cloud number instead.
  • The hub's template listing still shows these as sendable: false, because the inbox and post-call senders have no code to send. Sending one through this API works as shown above.

Templates with a media header or dynamic buttons

A template whose header is an image/video/document, or that has a dynamic URL button, needs richer per-message data than a flat variables array can carry. For these, pass a components array instead — this is Meta's runtime component format, where each object targets a template part (header, body, button) and lists its parameters.

For a media header, first upload the asset with POST /channels/{id}/media to get a mediaId (a UUID), then put that mediaId in the header parameter. Telenow translates it into WhatsApp's own media id at send time — you never handle a raw Meta media handle here. The upload must be on the same channel; if it's gone, the send fails with 502 (the image attached to this template is no longer available on this channel…). A value that isn't a UUID is passed to WhatsApp unchanged as a Meta media id, and a public link works too.

# 1) Upload the header image → mediaId
curl -X POST https://api.telenow.ai/api/app-whatsapp/channels/b1e2c3d4-…/media \
  -H "Authorization: Bearer vai_app_…" \
  -F "file=@/path/to/receipt.png"
{ "success": true, "data": { "mediaId": "5f0c2b1e-8d4a-4b6e-9c3f-2a7d1e0b9c84", "mime": "image/png", "filename": "receipt.png", "bytes": 84213 } }
# 2) Send the template, referencing that mediaId in the header component
curl -X POST https://api.telenow.ai/api/app-whatsapp/send-template \
  -H "Authorization: Bearer vai_app_…" \
  -H "Content-Type: application/json" \
  -d '{
    "channelId": "b1e2c3d4-…",
    "to": "+919876543210",
    "template": "order_receipt",
    "language": "en_US",
    "components": [
      {
        "type": "header",
        "parameters": [
          { "type": "image", "image": { "id": "5f0c2b1e-8d4a-4b6e-9c3f-2a7d1e0b9c84" } }
        ]
      },
      {
        "type": "body",
        "parameters": [
          { "type": "text", "text": "Asha" }
        ]
      }
    ]
  }'
{ "success": true, "data": { "wamid": "wamid.HBgLMTQxNTU1MDEwMBUCABIYFj…" } }
  • Use components instead of variables — put your body substitutions in the body component's parameters, not in a separate variables array.
  • For a video or document header, swap the parameter to { "type": "video", "video": { "id": "<mediaId>" } } or { "type": "document", "document": { "id": "<mediaId>" } }.
  • For a dynamic URL button, add a button component, e.g. { "type": "button", "sub_type": "url", "index": 0, "parameters": [ { "type": "text", "text": "1234" } ] } — the text fills the {{1}} in the button's URL.
  • components needs a Meta Cloud number; on any other number it returns 502.

With your org API key

With your org API key (X-API-Key: vai_live_…) the channel goes in the path, and the body is the same minus channelId:

curl -X POST https://api.telenow.ai/api/orgs/{orgId}/whatsapp-hub/channels/{channelId}/send-template \
  -H "X-API-Key: vai_live_…" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+919876543210",
    "template": "order_update",
    "language": "en_US",
    "variables": ["Asha", "#1234"]
  }'
{ "success": true, "data": { "wamid": "wamid.HBgLMTQxNTU1MDEwMBUCABIYFj…" } }
  • Inside a conversation: POST /api/orgs/{orgId}/whatsapp-hub/threads/{threadId}/send-template — same body without to.
  • Which templates a number can send: GET /api/orgs/{orgId}/whatsapp-hub/channels/{channelId}/templates works on every provider number. Each entry has ref (the value to send as template), label, language, category, body, variableCount and sendable. sendable is false, with an unsupportedReason, when a plain variables send can't fill the template (a media header or a dynamic button) — on a Meta Cloud number, send those with components. Meta Cloud numbers list approved templates; partner numbers list what the provider returns. To see every status, including pending and rejected, use the template management routes.
  • Partner numbers take variables only (components is Meta Cloud only). On AiSensy, template is the API campaign name.
  • Sends need an owner, admin or developer key.