Skip to main content

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​

CategoryUse case
UTILITYOrder updates, OTP, account alerts (lowest cost)
MARKETINGPromotions, offers, announcements
AUTHENTICATIONOne-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 — UTILITY is cheaper than MARKETING.
  • 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​

CodeDescriptionSolution
131026Template not approvedWait for approval / resubmit
132000Parameter count mismatchMatch parameters to placeholders
132012Parameter format invalidCheck parameter types
132068Template paused for qualityImprove content, re-enable

Need help? Contact Support → | Last Updated: May 2026