Documentation Index Fetch the complete documentation index at: https://docs.zavu.dev/llms.txt
Use this file to discover all available pages before exploring further.
Templates are pre-approved message formats required for contacting users outside the 24-hour conversation window. They must be submitted to Meta for approval before use.
Why Templates?
Required by WhatsApp - Only way to message users outside 24-hour window
Consistency - Ensure uniform messaging across your team
Compliance - Meta reviews content for quality and policy compliance
Analytics - Track performance by template
Quick Start: Send a Template Message
If you already have an approved template, here’s how to send it with variables:
TypeScript
Python
Ruby
Go
PHP
cURL
const message = await zavu . messages . send ({
to: "+56912345678" ,
messageType: "template" ,
content: {
templateId: "tpl_abc123" ,
templateVariables: {
"1" : "John" ,
"2" : "ORD-12345" ,
"3" : "January 20, 2025"
}
}
});
How Variables Work
Template variables use positional numbering : {{1}}, {{2}}, {{3}}, etc. When sending, pass the values as an object where keys are the position numbers:
Template body: "Hi {{1}}, your order #{{2}} is confirmed. Delivery: {{3}}."
^ ^ ^
| | |
Variables: "1": "John" "2": "ORD-12345" "3": "January 20, 2025"
You must provide values for all variables defined in the template. Missing variables will cause the message to fail.
Full Workflow
Templates follow a 3-step process: Create -> Submit for Approval -> Send .
Step 1: Create a Template
const template = await zavu . templates . create ({
name: "order_confirmation" ,
language: "en" ,
body: "Hi {{1}}, your order #{{2}} has been confirmed! Estimated delivery: {{3}}." ,
whatsappCategory: "UTILITY" ,
variables: [ "customer_name" , "order_id" , "delivery_date" ]
});
console . log ( template . id ); // "tpl_abc123"
console . log ( template . status ); // "draft"
The variables array is optional and purely for documentation - it helps you remember what each position represents.
Step 2: Submit for Approval
Templates start in draft status. You need to explicitly submit them to Meta for review:
const submitted = await zavu . templates . submit ( "tpl_abc123" , {
senderId: "sender_12345" ,
category: "UTILITY"
});
console . log ( submitted . status ); // "pending"
The senderId must be a sender with a WhatsApp Business Account connected. The template is submitted to Meta through that WABA.
Step 3: Check Approval Status
curl https://api.zavu.dev/v1/templates/tpl_abc123 \
-H "Authorization: Bearer zv_live_xxx"
{
"id" : "tpl_abc123" ,
"name" : "order_confirmation" ,
"status" : "approved" ,
"category" : "UTILITY" ,
"body" : "Hi {{1}}, your order #{{2}} has been confirmed! Estimated delivery: {{3}}." ,
"variables" : [ "customer_name" , "order_id" , "delivery_date" ]
}
Status Description draftCreated but not yet submitted to Meta pendingSubmitted, awaiting Meta review approvedReady to use rejectedNot approved - check rejection reason
Only approved templates can be used to send messages. Attempting to use draft, pending, or rejected templates will fail.
Step 4: Send the Template
Once approved, send it using the positional variables (see Quick Start above).
Template Categories
Category Use Case Approval Speed UTILITYOrder updates, shipping, account alerts Fast (hours) MARKETINGPromotions, offers, newsletters Slower (days) AUTHENTICATIONOTPs, verification codes Fast (hours)
Choose the category that best matches your use case. Miscategorization can lead to rejection.
Add call-to-action or quick reply buttons:
curl -X POST https://api.zavu.dev/v1/templates \
-H "Authorization: Bearer zv_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"name": "order_delivered",
"language": "en",
"body": "Hi {{1}}, your order has been delivered!",
"whatsappCategory": "UTILITY",
"variables": ["customer_name"],
"buttons": [
{
"type": "url",
"text": "Track Order",
"url": "https://example.com/track/{{1}}"
},
{
"type": "quick_reply",
"text": "Rate Delivery"
}
]
}'
Type Description Max quick_replyUser response button 3 buttons urlOpens a URL (can include variables) - phoneInitiates a phone call - otpCopy code / one-tap autofill for authentication -
When a template has a URL button with a {{1}} placeholder, pass the substitution under templateButtonVariables keyed by the button’s position in the template’s buttons array.
templateVariables is for body placeholders. templateButtonVariables is for buttons. They use different keys :
templateVariables keys → position of the placeholder in the body text ("1", "2", …).
templateButtonVariables keys → index of the button in the buttons array ("0", "1", "2").
WhatsApp URL buttons only accept {{1}} (positional, numeric, no whitespace, no name). Even though body placeholders may use named parameters like {{name}}, URL buttons do not. Anything else ({{token}}, {{ 1 }}, {{user.id}}, etc.) is approved by Meta as literal text in the URL — there is no way to substitute it later.If your template was approved with a non-{{1}} placeholder in a URL button, you must recreate the template with {{1}} and resubmit it for approval. Zavu now returns 400 invalid_request instead of silently delivering broken URLs like https://...%7B%7Bvariable%7D%7D.
Example. Suppose your approved template looks like this:
{
"id" : "tpl_discipline_report" ,
"body" : "Hi {{1}}, your child received a {{2}}." ,
"buttons" : [
{
"type" : "url" ,
"text" : "View report" ,
"url" : "https://reporte.link/{{1}}"
}
]
}
Send it like this:
await zavu . messages . send ({
to: "+56912345678" ,
messageType: "template" ,
content: {
templateId: "tpl_discipline_report" ,
templateVariables: {
"1" : "Marco Ordaz" ,
"2" : "report for inappropriate behavior"
},
templateButtonVariables: {
"0" : "abc-report-token"
}
}
});
The recipient receives a button that opens https://reporte.link/abc-report-token.
For templates with several buttons, supply one entry per dynamic button. Static URL buttons (no {{1}}) and quick_reply buttons are not included in templateButtonVariables.
{
"buttons" : [
{ "type" : "url" , "text" : "View order" , "url" : "https://shop.example.com/orders/{{1}}" },
{ "type" : "quick_reply" , "text" : "Track" },
{ "type" : "url" , "text" : "Help" , "url" : "https://help.example.com/" }
]
}
{
"templateButtonVariables" : {
"0" : "ORD-12345"
}
}
Index 1 (quick reply) and index 2 (static URL) are skipped.
Validation rules
Rule Behavior URL has {{1}} and templateButtonVariables[index] provided ✅ delivered with substitution URL has {{1}} and templateButtonVariables[index] missing ❌ 400 invalid_request: Missing URL button parameter at index N URL has {{name}}, {{ 1 }}, {{token}}, etc. ❌ 400 invalid_request: ... WhatsApp treats it as literal text. Recreate the template with "{{1}}" and resubmit it for approval. URL has more than one placeholder (e.g. /{{1}}/{{2}}) ❌ 400 invalid_request: at most one positional variable URL has no placeholder (static link) ✅ ignored — do not include the index in templateButtonVariables
Authentication Templates (OTP)
For verification codes, use AUTHENTICATION category with OTP buttons:
curl -X POST https://api.zavu.dev/v1/templates \
-H "Authorization: Bearer zv_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"name": "login_otp",
"language": "en",
"body": "{{1}} is your verification code.",
"whatsappCategory": "AUTHENTICATION",
"variables": ["otp_code"],
"buttons": [
{
"type": "otp",
"text": "Copy Code",
"otpType": "COPY_CODE"
}
],
"addSecurityRecommendation": true,
"codeExpirationMinutes": 5
}'
Send the OTP:
curl -X POST https://api.zavu.dev/v1/messages \
-H "Authorization: Bearer zv_live_xxx" \
-H "Content-Type: application/json" \
-d '{
"to": "+56912345678",
"messageType": "template",
"content": {
"templateId": "tpl_otp123",
"templateVariables": {
"1": "482910"
}
}
}'
See the OTP Templates guide for more details.
Managing Templates
List Templates
curl https://api.zavu.dev/v1/templates \
-H "Authorization: Bearer zv_live_xxx"
Delete Template
curl -X DELETE https://api.zavu.dev/v1/templates/tpl_abc123 \
-H "Authorization: Bearer zv_live_xxx"
Best Practices
Naming Conventions
Use descriptive, lowercase names with underscores:
order_confirmation
shipping_update
password_reset
appointment_reminder
promotional_offer
Content Guidelines
Do:
Use clear, concise language
Include dynamic variables for personalization
Provide value to the recipient
Test with real data before production use
Don’t:
Use placeholder text like “[insert name]”
Include excessive capitalization or punctuation
Send promotional content without consent
Use misleading or clickbait content
Common Rejection Reasons
Reason Solution Variable syntax error Use {{1}}, {{2}} positional format Category mismatch Choose correct category for content Promotional in UTILITY Use MARKETING category for promotions Missing opt-out Include unsubscribe option for marketing Policy violation Review Meta’s commerce and messaging policies
Testing Templates
Test with your own phone number before sending to customers:
curl -X POST https://api.zavu.dev/v1/messages \
-H "Authorization: Bearer zv_test_xxx" \
-H "Content-Type: application/json" \
-d '{
"to": "+your_phone_number",
"messageType": "template",
"content": {
"templateId": "tpl_abc123",
"templateVariables": {
"1": "Test User",
"2": "TEST-001",
"3": "Tomorrow"
}
}
}'
Use test mode API keys (zv_test_xxx) during development to avoid charges.