WhatsApp Templates
Templates are pre-approved message formats required for any business-initiated conversation or to reopen an expired 24-hour window. Each template must be submitted to WhatsApp for review before it can be sent.
📡 Endpoints
POST /v1/whatsapp/templates # Create a template (submits for approval)
GET /v1/whatsapp/templates # List templates and approval status
POST /v1/whatsapp/messages # Send an approved template
🏷️ Template Categories
| Category | Use case |
|---|---|
UTILITY | Order updates, OTP, account alerts (lowest cost) |
MARKETING | Promotions, offers, announcements |
AUTHENTICATION | One-time passcodes and login verification |
🧩 Components & Placeholders
Templates use numbered placeholders {{1}}, {{2}} for dynamic values. Supported components: HEADER (text/media), BODY, FOOTER, and BUTTONS.
✍️ Create a Template
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_confirmation",
"language": "en",
"category": "UTILITY",
"components": [
{
"type": "BODY",
"text": "Hi {{1}}, your order {{2}} has been confirmed and will ship soon."
},
{
"type": "FOOTER",
"text": "Reply STOP to opt out"
}
]
}'
Response
{
"id": "tpl_abc123",
"name": "order_confirmation",
"language": "en",
"category": "UTILITY",
"status": "PENDING"
}
Status values: PENDING, APPROVED, REJECTED, PAUSED, DISABLED
📤 Send an Approved Template
await fetch('https://api.afriroute.ai/api/v1/whatsapp/messages', {
method: 'POST',
headers: {
'Authorization': 'Bearer $AFRIROUTE_API_KEY',
'Content-Type': 'application/json'
},
body: JSON.stringify({
to: '+251911234567',
type: 'template',
template: {
name: 'order_confirmation',
language: 'en',
components: [
{
type: 'body',
parameters: [
{ type: 'text', text: 'John' },
{ type: 'text', text: '#12345' }
]
}
]
}
})
});
import requests
requests.post(
'https://api.afriroute.ai/api/v1/whatsapp/messages',
headers={'Authorization': 'Bearer $AFRIROUTE_API_KEY'},
json={
'to': '+251911234567',
'type': 'template',
'template': {
'name': 'order_confirmation',
'language': 'en',
'components': [{
'type': 'body',
'parameters': [
{'type': 'text', 'text': 'John'},
{'type': 'text', 'text': '#12345'}
]
}]
}
}
)
🖼️ Media & Button Headers
A template can carry a media header and call-to-action buttons:
{
"name": "shipping_update",
"language": "en",
"category": "UTILITY",
"components": [
{ "type": "HEADER", "format": "IMAGE", "example": { "header_url": "https://example.com/box.jpg" } },
{ "type": "BODY", "text": "Your parcel {{1}} is out for delivery." },
{ "type": "BUTTONS", "buttons": [
{ "type": "URL", "text": "Track", "url": "https://example.com/track/{{1}}" }
] }
]
}
💡 Best Practices
- Use clear placeholders —
{{1}},{{2}}for dynamic content. - Keep BODY under 1024 characters for higher approval rates.
- Pick the right category —
UTILITYis cheaper thanMARKETING. - Avoid promotional language in utility templates or risk rejection.
- Test in the sandbox number before submitting for production approval.
- Cache template metadata locally to cut API calls.
⚠️ Error Handling
| Code | Description | Solution |
|---|---|---|
| 131026 | Template not approved | Wait for approval / resubmit |
| 132000 | Parameter count mismatch | Match parameters to placeholders |
| 132012 | Parameter format invalid | Check parameter types |
| 132068 | Template paused for quality | Improve content, re-enable |
📚 Related Resources
Need help? Contact Support → | Last Updated: May 2026