Template messages

An approved template is the only way to reach a Cloud API contact outside the 24-hour window. This page is about sending one; creating them is covered under Templates.

Sending one

Reference the template by its id in this platform — not by its Meta name. It must be APPROVED.

cURL
curl -X POST https://api.wpai.co.in/api/messages/send \
  -H "X-API-Key: $WPAI_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "919876543210",
    "channel": "whatsapp_cloud",
    "type": "template",
    "templateId": "68a1f3…",
    "variableMapping": {
      "1": "name",
      "2": "literal:4471",
      "3": "literal:29 August"
    }
  }'

Find template ids with GET /api/templates. Templates are Cloud API only — asking for one on WhatsApp Web returns a 400 telling you to send text instead.

Filling variables

A template body contains numbered placeholders — {{1}}, {{2}} — fixed at approval time. variableMapping says what goes in each: the key is the number as a string, the value names a source rather than being the text itself.

Body approved as
Hi {{1}}, your order {{2}} will arrive on {{3}}.
variableMapping
{ "1": "name", "2": "literal:4471", "3": "literal:29 August" }

Keys are sorted numerically before being sent, so "10" correctly follows "2" rather than "1".

An empty value becomes a hyphen

Meta rejects empty template parameters outright, so a source that resolves to an empty string is sent as the literal "-". If your customers are receiving messages containing a stray hyphen, a variable source is resolving to nothing.

Variable sources

SourceResolves to
nameThe contact’s name, or “there” when it is empty.
phoneThe contact’s number.
custom:<key>A custom field on the contact record.
literal:<text>Exactly the text after the prefix.
anything elseThe string itself, used verbatim.

custom: does not work on this endpoint

A send resolves variables against only the contact's name and number — no custom fields are loaded here, so every custom: source resolves to empty and is sent as "-". Pass the value with literal: instead; you already know it.

Unmapped placeholders are left in place rather than blanked, so a missing mapping shows up as a visible {{2}} in the message rather than vanishing silently.

Templates with a media header

A template whose header is an image, video or document needs a header parameter naming the media. There are three ways to supply it, and they are tried in this order.

1. A public URL. Meta fetches it, so there is no upload step and nothing to keep track of. This is the one to reach for.

json
{
  "to": "919876543210",
  "type": "template",
  "templateId": "<templateId>",
  "headerMediaUrl": "https://files.example.com/banners/diwali.jpg",
  "variableMapping": { "1": "name" }
}

2. An uploaded media id. Upload once to POST /api/campaigns/media and reuse the media_id it returns for about 30 days. Worth it when the same asset goes out repeatedly.

json
{
  "to": "919876543210",
  "type": "template",
  "templateId": "<templateId>",
  "headerMediaId": "<media_id>"
}

3. Nothing at all. Send neither field and the sample you uploaded when the template was created is used — the same media the dashboard sends. Convenient, but it costs an upload on each send, so prefer a URL for anything high-volume.

The handle from template creation is not this

Meta issues two different media identifiers. The header_handle returned when you create a template is write-only and valid only for that review; a send needs a media_id or a URL. Passing the handle here is accepted by us and rejected by Meta with error 132000.

One caveat that still applies: a text header containing {{1}} is not parameterised — only the body is. Prefer a static text header for anything you will send this way.

What it costs

This is the one send that spends money. The rate depends on the destination country and the template's category, and the balance is checked before Meta is called:

402
{ "error": "Insufficient wallet balance. Send costs ₹0.88." }

The debit happens after Meta accepts it, categorised as message_marketing, message_utility or message_authentication, and shown against your balance in the dashboard.

What can go wrong

StatusErrorCause
400Parameter "templateId" is required for template message typeMissing id.
400Template is not APPROVED by MetaStill pending, rejected, or paused.
400WhatsApp Web has no templates — send type "text" insteadWrong channel.
404Template not foundUnknown id, or it belongs to another organization.
402Insufficient wallet balance…Top up before retrying.
502Template send failed: …Meta rejected it. The message carries their text.

A 502 here does not flag a dead token

Unlike text and media, a template send that fails on an expired access token does not mark the number disconnected. If templates start failing while everything else works, check the number's status on the Channels screen — an expired token has to be reconnected there.