Skip to content
Send templates

Create templates

Use POST /v1/{phone_number_id}/templates to author a template programmatically. On success Kirimdev persists the row with status: "pending" and forwards the structure to Meta for review. Poll GET /v1/{phone_number_id}/templates/{name} (or run POST .../templates/sync) until Meta sets approved or rejected.

This page covers creation. To send an already-approved template (including runtime header images), see Send templates and Header media at send time.

Terminal window
curl -X POST "https://api.kirimdev.com/v1/$PHONE_ID/templates" \
-H "Authorization: Bearer $KIRIM_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "order_update",
"category": "UTILITY",
"language": "id",
"components": [
{
"type": "BODY",
"text": "Pesanan {{1}} sedang diproses.",
"example": { "body_text": [["A123"]] }
}
]
}'

A duplicate name + language for the account returns 409 template_already_exists before any Meta call.

Meta requires a sample asset at creation time. Kirimdev accepts a public https:// URL, downloads the file server-side, uploads it to Meta via the Resumable Upload API, and submits the resulting asset handle to Meta on your behalf. You only need your Kirim API key — no direct Meta token required.

Preferred field: example.header_url

{
"name": "promo_gambar",
"category": "MARKETING",
"language": "id",
"components": [
{
"type": "HEADER",
"format": "IMAGE",
"example": {
"header_url": ["https://cdn.example.com/banners/juni.jpg"]
}
},
{
"type": "BODY",
"text": "Halo {{1}}, ada promo spesial hari ini.",
"example": { "body_text": [["Budi"]] }
}
]
}

Backward-compatible alias: you may place the same public URL in example.header_handle instead of header_url. Kirimdev detects https:// values and treats them as URLs to fetch.

ConstraintDetail
Schemehttps:// (public internet; private IPs blocked)
ReachabilityKirimdev servers must be able to GET the URL once at create time
IMAGEJPEG or PNG, ≤ 5 MB (WebP rejected by Meta for samples)
VIDEOMP4 or 3GPP, ≤ 16 MB
DOCUMENTPDF and common office formats, ≤ 100 MB
Content-TypeServed by the origin, or inferable from the file extension

If the URL is unreachable, the wrong type, or too large, the API returns 400 invalid_field_value with param: "components".

If you uploaded the asset yourself via Meta’s Resumable Upload API, pass the handle verbatim:

"example": { "header_handle": ["4::aW1hZ2UvanBlZw==:YOUR_HANDLE"] }

Kirimdev passes it through without re-uploading.

SituationHTTPerror.code
Invalid body / Meta rejects template content400invalid_field_value
Bad or unreachable header media URL400invalid_field_value
Duplicate name + language (create)409template_already_exists
Editing a template still under review (edit)409template_not_editable
Meta unreachable / transient upstream fault502whatsapp_upstream_error

Meta validation failures (wrong examples, format rules) surface as 400, not 502 — retrying the same payload will not succeed until the components are fixed.

Use PATCH /v1/{phone_number_id}/templates/{name} to change an existing template’s content and re-submit it to Meta. The request body is components-only — send the full desired components array. Meta replaces all components at once, so this is a replacement, not a partial patch: omitting a component removes it.

name, language, and category cannot change. When a name has multiple languages, pass ?language= to pick which one to edit.

Terminal window
curl -X PATCH "https://api.kirimdev.com/v1/$PHONE_ID/templates/order_update?language=id" \
-H "Authorization: Bearer $KIRIM_KEY" \
-H "Content-Type: application/json" \
-d '{
"components": [
{
"type": "BODY",
"text": "Pesanan {{1}} sudah dikirim.",
"example": { "body_text": [["A123"]] }
}
]
}'

A successful edit puts the template back into Meta review, so the returned status is pending; poll GET /v1/{phone_number_id}/templates/{name} for the eventual approved / rejected transition.

A carousel template is a MARKETING message with 2–10 swipeable product cards. Each card has its own image or video header, an optional body text, and up to two buttons (quick reply, URL, or phone number). Add a CAROUSEL component next to the message BODY:

{
"name": "rare_succulents_carousel",
"language": "en_US",
"category": "MARKETING",
"components": [
{ "type": "BODY", "text": "Rare succulents for sale!" },
{
"type": "CAROUSEL",
"cards": [
{
"components": [
{ "type": "HEADER", "format": "IMAGE",
"example": { "header_url": ["https://cdn.example.com/aloe.jpg"] } },
{ "type": "BUTTONS", "buttons": [
{ "type": "QUICK_REPLY", "text": "Send more like this" },
{ "type": "URL", "text": "Shop", "url": "https://shop.example.com/aloe" }
] }
]
}
// ...at least one more card, same structure
]
}
]
}

Rules Meta enforces (validated up front):

  • 2–10 cards, and the approved template always sends exactly that many.
  • Every card shares the same structure — same header format and the same button types in the same order.
  • All-or-nothing card body — if one card has body text, all must.
  • Carousels are MARKETING-only and cannot carry a top-level HEADER / FOOTER / BUTTONS (put those on the cards).
  • Provide each card header as example.header_url (public https:// URL); Kirimdev uploads it to Meta and keeps the URL so the template stays sendable. A dynamic card URL button ({{1}} suffix) needs an example value, just like a top-level dynamic URL button.

See Send templates for how to fill each card at send time.

The Kirimdev dashboard can also create templates and accepts a media library file via headerMediaId on the internal API. The Public API does not expose headerMediaId; use example.header_url or create from the dashboard UI instead.

Category AUTHENTICATION is for one-time passwords. Meta auto-approves these templates and generates the body text; you configure the security disclaimer, optional expiry footer, and OTP button. Full create + send examples live on OTP (authentication templates).

If Meta rejects creation, inspect the API error’s message. Kirimdev returns Meta’s most specific available validation detail, which may identify an invalid component, OTP button setting, example value, or another rejected parameter. The generic Invalid parameter text is used only when Meta does not provide a more precise explanation.