Designing & Approving WhatsApp Templates
Business-initiated WhatsApp messages must use a pre-approved template. This guide shows how to structure templates, pick the right category, and avoid the most common rejection reasons.
🚀 Quick Start
curl -X POST https://api.afriroute.ai/api/v1/whatsapp/templates \
-H "Authorization: Bearer $AFRIROUTE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "order_shipped",
"category": "UTILITY",
"language": "en",
"body": "Hi {{1}}, your order {{2}} has shipped and arrives {{3}}."
}'
🗂️ Categories
WhatsApp routes and prices templates by category. Choosing wrong is the top cause of rejection.
| Category | Use for | Examples |
|---|---|---|
UTILITY | Transaction follow-ups | Order updates, OTP, appointment reminders |
MARKETING | Promotions | Offers, newsletters, re-engagement |
AUTHENTICATION | One-time passcodes | Login codes, verification |
Putting promotional content in a UTILITY template will get it rejected or recategorized.
🔤 Variables
Variables use {{1}}, {{2}} numbered placeholders, filled at send time.
await fetch('https://api.afriroute.ai/api/v1/whatsapp/send', {
method: 'POST',
headers: {
'Authorization': 'Bearer $AFRIROUTE_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
to: '+254712345678',
template: 'order_shipped',
language: 'en',
variables: ['Amina', '#A1029', 'Friday']
})
});
import requests
requests.post(
'https://api.afriroute.ai/api/v1/whatsapp/send',
headers={'Authorization': 'Bearer $AFRIROUTE_API_KEY'},
json={
'to': '+234803000000',
'template': 'order_shipped',
'language': 'en',
'variables': ['Bola', '#A1030', 'Monday']
}
)
Rules that cause rejection:
- No two variables adjacent:
{{1}} {{2}}is fine,{{1}}{{2}}is not. - A template cannot be only variables — it needs fixed text.
- Variables cannot start or end the message body.
🧱 Components
A template can include a header (text, image, or document), a body, a footer, and buttons.
{
"name": "appointment_reminder",
"category": "UTILITY",
"language": "en",
"header": { "type": "text", "text": "Appointment Reminder" },
"body": "Hi {{1}}, your visit is on {{2}} at {{3}}.",
"footer": "Reply CANCEL to reschedule",
"buttons": [
{ "type": "QUICK_REPLY", "text": "Confirm" },
{ "type": "QUICK_REPLY", "text": "Reschedule" }
]
}
💡 Best Practices
- Match content to category — the most common rejection reason.
- Add sample variable values on submission so reviewers see real intent.
- Keep body concise — long, salesy
UTILITYtemplates get downgraded. - Pre-translate and submit a template per language you support.
- Re-use approved templates instead of creating near-duplicates.
⚠️ Handling Rejections
{
"name": "order_shipped",
"status": "REJECTED",
"reason": "INVALID_FORMAT",
"detail": "Body ends with a variable"
}
Fix the flagged issue and resubmit — name reuse is allowed once the prior version is rejected.
📚 Related Resources
Last Updated: May 2026