Template messages
Sending one
Reference the template by its id in this platform — not by its Meta name. It must be APPROVED.
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.
Hi {{1}}, your order {{2}} will arrive on {{3}}.{ "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
"-". If your customers are receiving messages containing a stray hyphen, a variable source is resolving to nothing.Variable sources
| Source | Resolves to |
|---|---|
name | The contact’s name, or “there” when it is empty. |
phone | The contact’s number. |
custom:<key> | A custom field on the contact record. |
literal:<text> | Exactly the text after the prefix. |
anything else | The string itself, used verbatim. |
custom: does not work on this endpoint
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.
{
"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.
{
"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
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:
{ "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
| Status | Error | Cause |
|---|---|---|
400 | Parameter "templateId" is required for template message type | Missing id. |
400 | Template is not APPROVED by Meta | Still pending, rejected, or paused. |
400 | WhatsApp Web has no templates — send type "text" instead | Wrong channel. |
404 | Template not found | Unknown id, or it belongs to another organization. |
402 | Insufficient wallet balance… | Top up before retrying. |
502 | Template send failed: … | Meta rejected it. The message carries their text. |
A 502 here does not flag a dead token