Send templates
Templates are pre-approved message structures you use to start a conversation, send transactional notifications, or re-engage past customers — anything outside the 24-hour customer service window.
This page covers the simplest case: a body-only template with text parameters. For richer variants — header media, dynamic URL buttons, Flow buttons, named parameters — see the dedicated pages below.
Find your template
Section titled “Find your template”List templates already approved for your WhatsApp account:
curl https://api.kirimdev.com/v1/$PHONE_ID/templates \ -H "Authorization: Bearer $KIRIM_KEY"Each template returns its name, language, status (approved /
pending / rejected), and the parameterised body.
Name rules
Section titled “Name rules”template.name is a Meta identifier, not free text. It may contain only
lowercase letters, numbers, and underscores (^[a-z0-9_]+$).
Surrounding whitespace is trimmed for you, so a name pasted with a stray
trailing space still sends. Anything trimming cannot repair — interior
spaces, uppercase letters, dashes — is rejected up front with
400 invalid_request_error rather than being forwarded to Meta, which
answers malformed names with a generic “Something went wrong” that is
indistinguishable from a real upstream outage.
"name": "order_shipped" // ok"name": "order_shipped " // ok - trimmed"name": "order shipped" // 400"name": "Order_Shipped" // 400"name": "order-shipped" // 400Send a body-only template
Section titled “Send a body-only template”curl -X POST \ https://api.kirimdev.com/v1/$PHONE_ID/messages \ -H "Authorization: Bearer $KIRIM_KEY" \ -H "Content-Type: application/json" \ -d '{ "messaging_product": "whatsapp", "to": "+628123456789", "type": "template", "template": { "name": "order_shipped", "language": "id", "components": [ { "type": "body", "parameters": [ { "type": "text", "text": "Andi" }, { "type": "text", "text": "INV-4521" } ] } ] } }'import { Kirim } from '@kirimdev/sdk'
const kirim = new Kirim({ apiKey: process.env.KIRIM_KEY! })const phone = kirim.phoneNumbers(process.env.PHONE_ID!)
await phone.messages.send({ messaging_product: 'whatsapp', to: '+628123456789', type: 'template', template: { name: 'order_shipped', language: 'id', components: [ { type: 'body', parameters: [ { type: 'text', text: 'Andi' }, { type: 'text', text: 'INV-4521' }, ], }, ], },})Positional parameters fill the {{1}}, {{2}}, … placeholders in the
template body in declaration order.
Parameter checks before sending
Section titled “Parameter checks before sending”When the template is approved, Kirimdev compares your components with it
before accepting the request. A payload WhatsApp would reject for every
recipient is answered with 400 invalid_request_error (code
invalid_field_value) instead of 202 followed by a failed status:
- The
headerorbodyparameter count differs from the template’s variables. - A
textparameter is missing,null, or blank. - A
textparameter contains a newline, a tab, or more than 4 consecutive spaces. - A media header has no parameter.
- A URL button with a variable has no parameter, or it is blank.
The error message names the component, for example
Body expects 2 parameter(s), got 1. Authentication and carousel templates
are not checked here; Meta still validates them.
Component shapes at a glance
Section titled “Component shapes at a glance”Templates support three component types. Each takes a parameters
array whose shape mirrors Meta’s
Cloud API reference
verbatim — Kirimdev does not re-shape it.
| Component | Purpose | Dedicated page |
|---|---|---|
header | Media or text shown above the body | Header media |
body | Parameterised text content | This page |
button (per index) | Dynamic URL, quick reply, copy code | Buttons |
button (sub_type: url) | OTP / verification code (AUTHENTICATION) | OTP |
button (sub_type: flow) | Trigger a Meta Flow | Flow buttons |
Parameters can be positional (default) or named — see Named parameters.
Media card carousels
Section titled “Media card carousels”To send an approved media card carousel,
add a carousel component whose cards array carries one entry per card,
keyed by card_index. Each card supplies its header asset (an uploaded
media id or a public link) and a parameter for every quick-reply
button (payload) and dynamic URL button (text):
{ "messaging_product": "whatsapp", "to": "+16505551234", "type": "template", "template": { "name": "rare_succulents_carousel", "language": { "code": "en_US" }, "components": [ { "type": "carousel", "cards": [ { "card_index": 0, "components": [ { "type": "header", "parameters": [{ "type": "image", "image": { "id": "1558081531584829" } }] }, { "type": "button", "sub_type": "quick_reply", "index": 0, "parameters": [{ "type": "payload", "payload": "more-aloes" }] } ] }, { "card_index": 1, "components": [ { "type": "header", "parameters": [{ "type": "image", "image": { "link": "https://cdn.example.com/echeveria.jpg" } }] }, { "type": "button", "sub_type": "quick_reply", "index": 0, "parameters": [{ "type": "payload", "payload": "more-echeverias" }] } ] } ] } ] }}You must send exactly the number of cards the template was approved
with. A card header accepts either an uploaded media id
(POST /{phone_number_id}/media) or a public link. If a card’s URL
button was created dynamic ({{1}} suffix), add a
{ "type": "button", "sub_type": "url", "index": N, "parameters": [{ "type": "text", "text": "<value>" }] }
for it. A static button (static URL / phone number) needs no send-time
parameter.
Language object form
Section titled “Language object form”Meta accepts language as either a bare string code ("id",
"en_US") or an object { code, policy? }. Kirimdev accepts both -
prefer the string form since policy was deprecated by Meta.