Skip to content
Send templates

Send OTP (authentication templates)

Authentication templates are the only supported way to send OTP / verification codes on WhatsApp. They use category AUTHENTICATION, have fixed body copy generated by Meta, and usually include a copy-code button that Meta stores internally as a URL button at approval time.

Kirimdev forwards your components array to Meta verbatim — the send shape below matches what Meta’s authentication template guide expects. It is not the same as marketing/utility copy_code buttons documented on Buttons.

  1. An approved template with category AUTHENTICATION on the WhatsApp account behind $PHONE_ID.
  2. The exact language code Meta approved (e.g. id, en_US) — confirm with GET /v1/{phone_number_id}/templates/{name}.

Authentication templates are auto-approved by Meta (no 24-hour marketing review). Body text is fixed; you only configure security disclaimer, optional expiry footer, and the OTP button type.

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": "otp_verification",
"category": "AUTHENTICATION",
"language": "id",
"messageSendTtlSeconds": 600,
"components": [
{ "type": "BODY", "add_security_recommendation": true },
{ "type": "FOOTER", "code_expiration_minutes": 5 },
{
"type": "BUTTONS",
"buttons": [{ "type": "OTP", "otp_type": "COPY_CODE" }]
}
]
}'

After creation (or POST .../templates/sync), inspect GET .../templates/otp_verification. The BUTTONS component typically shows type: "URL" with a WhatsApp OTP URL containing otp{{1}} — that is normal.

Pass the same one-time code in two places:

  1. bodyparameters[0].text
  2. buttonsub_type: "url", index: 0, parameters[0].text

The code must be ≤ 15 characters (Meta limit for authentication parameters). No URLs, media, or emoji in the value.

Terminal window
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": "otp_verification",
"language": "id",
"components": [
{
"type": "body",
"parameters": [
{ "type": "text", "text": "847291" }
]
},
{
"type": "button",
"sub_type": "url",
"index": 0,
"parameters": [
{ "type": "text", "text": "847291" }
]
}
]
}
}'

The API returns status: "pending" immediately; poll GET /v1/{phone_number_id}/messages/{id} or subscribe to a message.status webhook for sent / failed.

If your approved template has no dynamic button (uncommon for OTP), send only the body component and omit the button block entirely.

MistakeFix
sub_type: "copy_code" + coupon_code on an AUTHENTICATION templateUse sub_type: "url" and parameters[].type: "text" (see above)
copy_code works for MARKETING/UTILITY coupon buttons onlySee Buttons — different category
OTP in body ≠ OTP in buttonBoth values must match
Wrong language (id vs id_ID)Copy from GET .../templates/{name}
Button index not 0Match the button order in the approved template
Extra button block when template has no {{ in button URLDrop the button component