Templates
Base: /v2/templates
Auth + Premium/Enterprise + active plan required. POST routes also require phoneNumberId ownership — see overview — Phone number ownership and Account.
Also read: HTTP API v2 Overview
GET /v2/templates
Query (optional)
| Name | Description |
|---|---|
limit | Passed to Meta message templates API when set |
after | Cursor for paging |
search | Case-insensitive substring filter on template name (applied after fetch) |
Example request:
bash
curl -sS "https://api.avenping.com/v2/templates?limit=10&search=order" \
-H "Authorization: Bearer YOUR_TOKEN"Response 200 — items are Meta template objects; paging mirrors Meta when present.
json
{
"success": true,
"data": {
"items": [
{
"name": "order_confirmation",
"components": [],
"language": "en_US",
"status": "APPROVED",
"category": "UTILITY",
"id": "123456789012345"
}
],
"paging": {
"cursors": {
"before": "QVFIUz...",
"after": "QVFIUz..."
},
"next": "https://graph.facebook.com/..."
}
}
}GET /v2/templates/:name
Example request:
bash
curl -sS "https://api.avenping.com/v2/templates/order_confirmation" \
-H "Authorization: Bearer YOUR_TOKEN"Response 200 — single Meta template object in data (shape similar to one element of items above).
Response 404:
json
{
"success": false,
"error": "NOT_FOUND",
"message": "Template not found"
}POST /v2/templates/send
Body (JSON)
| Field | Type | Required | Notes |
|---|---|---|---|
phoneNumberId | string | yes | Meta phone number ID (must belong to account) |
to | string | yes | E.164-like; non-digits stripped |
templateName | string | yes | Template name |
language | string | no | Default en_US |
components | array | no | Meta send-time template components (see below). Omitted or [] only when the template has no variables. |
contextMessageWamid | string | no | Reply context wamid |
How this relates to template example fields
GET /v2/templates and GET /v2/templates/:name return Meta template definition objects. Those definitions often include example data (sample text, sample media handles, URL suffix samples, and so on) so Meta and tooling can preview the template.
POST /v2/templates/send does not read or apply those examples. The route validates the JSON body, strips non-digits from to, and forwards components unchanged inside the Cloud API message payload.
Practical workflow:
- Call
GET /v2/templates/:name(or list and pick one) to seecomponents, variables, and anyexampleblocks. - Map each variable your template expects to a real value (production) or, for sandbox tests, you may temporarily reuse values from
example—they are not substituted automatically. - Omit
componentsor send[]only if the template has no header/body/button/carousel/LTO parameters.
Send payloads use lowercase type values on each component: header, body, button, carousel, limited_time_offer, etc., as required by the messages API.
Example: components by template shape
The following snippets are illustrative; field names must match your approved template (language code, number of parameters, button order).
Static template (no variables) — omit components or use an empty array:
json
{
"phoneNumberId": "109876543210987",
"to": "+15551234567",
"templateName": "hello_world",
"language": "en_US",
"components": []
}Body only (positional 1, 2)
json
{
"phoneNumberId": "109876543210987",
"to": "+15551234567",
"templateName": "order_shipped",
"language": "en_US",
"components": [
{
"type": "body",
"parameters": [
{ "type": "text", "text": "Alex" },
{ "type": "text", "text": "TRACK123" }
]
}
]
}Body with named parameters (when the template uses named variables—include parameter_name as in Meta’s spec)
json
{
"type": "body",
"parameters": [
{
"type": "text",
"parameter_name": "customer_name",
"text": "Alex"
}
]
}Header: TEXT
json
{
"type": "header",
"parameters": [{ "type": "text", "text": "Weekly deals" }]
}Header: IMAGE / VIDEO / DOCUMENT — use a media id from POST /v2/media (or your own upload to Meta), not the template’s sample header_handle URL, unless your integration explicitly uses handles as allowed by Meta for your account/version.
json
{
"type": "header",
"parameters": [
{ "type": "image", "image": { "id": "1234567890" } }
]
}Header: PRODUCT (catalog product in header)
json
{
"type": "header",
"parameters": [
{
"type": "product",
"product": {
"product_retailer_id": "sku-123"
}
}
]
}Button: dynamic URL (https://example.com/1)
json
{
"type": "button",
"sub_type": "url",
"index": "0",
"parameters": [{ "type": "text", "text": "invoices/INV-9" }]
}Button: COPY_CODE
json
{
"type": "button",
"sub_type": "copy_code",
"index": "1",
"parameters": [{ "type": "coupon_code", "coupon_code": "SAVE20" }]
}Button: CATALOG (optional thumbnail product)
json
{
"type": "button",
"sub_type": "catalog",
"index": "0",
"parameters": [
{
"type": "action",
"action": {
"thumbnail_product_retailer_id": "sku-123"
}
}
]
}Button: MPM (multi-product message; shape must match your template and catalog—often includes thumbnail_product_retailer_id and sections with product_items)
json
{
"type": "button",
"sub_type": "mpm",
"index": "0",
"parameters": [
{
"type": "action",
"action": {
"thumbnail_product_retailer_id": "sku-123",
"sections": [
{
"title": "Featured",
"product_items": [{ "product_retailer_id": "sku-123" }]
}
]
}
}
]
}Body: currency / date_time (when the template was created with those variable types)
json
{
"type": "body",
"parameters": [
{
"type": "currency",
"currency": {
"fallback_value": "$99.99",
"code": "USD",
"amount_1000": 99990
}
},
{
"type": "date_time",
"date_time": { "fallback_value": "April 10, 2026" }
}
]
}Quick-reply buttons on a template usually require no button component in the send payload unless Meta’s spec for your template version says otherwise.
Carousel — top-level body (parameters or empty array if required by the template) plus carousel with cards; each card contains its own components (typically header with media or product, and body parameters).
json
{
"components": [
{
"type": "body",
"parameters": [{ "type": "text", "text": "Spring collection" }]
},
{
"type": "carousel",
"cards": [
{
"card_index": 0,
"components": [
{
"type": "header",
"parameters": [
{ "type": "image", "image": { "id": "MEDIA_ID_CARD_0" } }
]
},
{
"type": "body",
"parameters": [{ "type": "text", "text": "Item A" }]
}
]
}
]
}
]
}Limited-time offer — limited_time_offer component with expiration, plus any required copy_code / dynamic url buttons your template defines:
json
{
"components": [
{
"type": "limited_time_offer",
"parameters": [
{
"type": "limited_time_offer",
"limited_time_offer": { "expiration_time_ms": 1712876400000 }
}
]
},
{
"type": "button",
"sub_type": "copy_code",
"index": "0",
"parameters": [{ "type": "coupon_code", "coupon_code": "FLASH10" }]
}
]
}TypeScript: using example only as dev defaults (optional)
This does not run on the server; it shows how you might derive placeholder strings from a template definition when building components for tests. Always send real user data in production.
typescript
type MetaTemplate = {
name?: string;
language?: string;
components?: any[];
};
/** Pull sample strings from Meta template definition `example` blocks — for local/sandbox only. */
function devDefaultsFromTemplate(template: MetaTemplate): {
bodyTexts: string[];
headerText?: string;
urlSuffixByButtonIndex: Record<number, string>;
} {
const bodyTexts: string[] = [];
let headerText: string | undefined;
const urlSuffixByButtonIndex: Record<number, string> = {};
for (const c of template.components ?? []) {
const t = String(c?.type ?? "").toUpperCase();
if (t === "BODY" && c.example) {
const named = c.example.body_text_named_params;
const positional = c.example.body_text;
if (Array.isArray(named)) {
for (const row of named) {
if (row?.example != null) bodyTexts.push(String(row.example));
}
} else if (Array.isArray(positional) && Array.isArray(positional[0])) {
for (const x of positional[0]) bodyTexts.push(String(x));
}
}
if (t === "HEADER" && (c.format === "TEXT" || c.format === "text") && c.example?.header_text?.[0]) {
headerText = String(c.example.header_text[0]);
}
if (t === "BUTTONS" && Array.isArray(c.buttons)) {
c.buttons.forEach((b: any, i: number) => {
const bt = String(b?.type ?? "").toUpperCase();
if (bt === "URL" && Array.isArray(b.example) && b.example[0]) {
urlSuffixByButtonIndex[i] = String(b.example[0]);
}
});
}
}
return { bodyTexts, headerText, urlSuffixByButtonIndex };
}
/** For named body variables: map template `example.body_text_named_params` → send parameters. */
function devNamedBodyParameters(template: MetaTemplate) {
const params: { type: "text"; parameter_name: string; text: string }[] = [];
for (const c of template.components ?? []) {
if (String(c?.type ?? "").toUpperCase() !== "BODY") continue;
const rows = c.example?.body_text_named_params;
if (!Array.isArray(rows)) continue;
for (const row of rows) {
const name = row?.param_name ?? row?.parameter_name;
if (name && row?.example != null) {
params.push({
type: "text",
parameter_name: String(name),
text: String(row.example),
});
}
}
}
return params;
}
/** Minimal send body for AvenPing `POST /v2/templates/send`. */
function buildSendBody(opts: {
phoneNumberId: string;
to: string;
templateName: string;
language?: string;
components: unknown[];
}) {
return {
phoneNumberId: opts.phoneNumberId,
to: opts.to,
templateName: opts.templateName,
language: opts.language ?? "en_US",
components: opts.components,
};
}
// Example: fetch template, then build components using dev defaults for body only.
async function exampleFlow(apiBase: string, token: string, name: string) {
const res = await fetch(`${apiBase}/v2/templates/${encodeURIComponent(name)}`, {
headers: { Authorization: `Bearer ${token}` },
});
const json = await res.json();
const template: MetaTemplate = json.data;
const { bodyTexts, headerText, urlSuffixByButtonIndex } = devDefaultsFromTemplate(template);
const components: any[] = [];
if (headerText) {
components.push({ type: "header", parameters: [{ type: "text", text: headerText }] });
}
if (bodyTexts.length) {
components.push({
type: "body",
parameters: bodyTexts.map((text) => ({ type: "text", text })),
});
}
Object.entries(urlSuffixByButtonIndex).forEach(([idx, text]) => {
components.push({
type: "button",
sub_type: "url",
index: String(idx),
parameters: [{ type: "text", text }],
});
});
return buildSendBody({
phoneNumberId: "YOUR_PHONE_NUMBER_ID",
to: "+15551234567",
templateName: name,
language: template.language,
components,
});
}Media headers cannot be filled from example.header_handle alone in most integrations—you still upload media and pass { type: "image", image: { id } } (or video/document).
If the template uses named body variables, build { type: "body", parameters: devNamedBodyParameters(template) } instead of the flat bodyTexts list from devDefaultsFromTemplate.
Example request (curl, static template):
bash
curl -sS -X POST "https://api.avenping.com/v2/templates/send" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"phoneNumberId": "109876543210987",
"to": "+1 555 123 4567",
"templateName": "hello_world",
"language": "en_US",
"components": []
}'Response 200 — data is Meta’s successful messages API response (illustrative):
json
{
"success": true,
"data": {
"messaging_product": "whatsapp",
"contacts": [
{
"input": "15551234567",
"wa_id": "15551234567"
}
],
"messages": [
{
"id": "wamid.HBgL..."
}
]
}
}POST /v2/templates/:name/test-send
Same body as /send except templateName is taken from the path :name.
Example request:
bash
curl -sS -X POST "https://api.avenping.com/v2/templates/hello_world/test-send" \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"phoneNumberId": "109876543210987",
"to": "15551234567",
"language": "en_US"
}'Response 200 — same envelope as template send; data is Meta’s send response.
Related
- Account
- Messages
- Media (upload ids for template media headers)
- HTTP API v2 Overview
- Meta: Send template messages (Cloud API)