Skip to main content

Interactive Messages

Interactive messages add tappable buttons, selectable lists, and call-to-action links to your conversations. They drive higher engagement than plain text and make webhook handling deterministic through predictable reply IDs. Interactive messages require an open 24-hour window.

📡 Endpoint​

POST /v1/whatsapp/messages

All interactive messages use "type": "interactive" with an interactive object whose type is one of button, list, or cta_url.

🔘 Quick Reply Buttons​

Up to 3 buttons. Button titles must be 20 characters or fewer.

{
"to": "+251911234567",
"type": "interactive",
"interactive": {
"type": "button",
"body": { "text": "How can we help you today?" },
"action": {
"buttons": [
{ "type": "reply", "reply": { "id": "support", "title": "Contact Support" } },
{ "type": "reply", "reply": { "id": "track", "title": "Track Order" } }
]
}
}
}

📋 List Messages​

Use lists for 4–10 options grouped into sections.

{
"to": "+251911234567",
"type": "interactive",
"interactive": {
"type": "list",
"header": { "type": "text", "text": "Support" },
"body": { "text": "Choose a department:" },
"footer": { "text": "We reply within 5 minutes" },
"action": {
"button": "View Options",
"sections": [
{
"title": "Departments",
"rows": [
{ "id": "sales", "title": "Sales", "description": "Pricing and plans" },
{ "id": "support", "title": "Support", "description": "Technical help" },
{ "id": "billing", "title": "Billing", "description": "Invoices and payments" }
]
}
]
}
}
}

🔗 Call-to-Action URL​

{
"to": "+251911234567",
"type": "interactive",
"interactive": {
"type": "cta_url",
"body": { "text": "Your order has shipped." },
"action": {
"name": "cta_url",
"parameters": { "display_text": "Track Package", "url": "https://example.com/track/12345" }
}
}
}

💻 Code Samples​

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: 'interactive',
interactive: {
type: 'button',
body: { text: 'Confirm your appointment?' },
action: {
buttons: [
{ type: 'reply', reply: { id: 'confirm', title: 'Confirm' } },
{ type: 'reply', reply: { id: 'reschedule', title: 'Reschedule' } }
]
}
}
})
});
import requests

requests.post(
'https://api.afriroute.ai/api/v1/whatsapp/messages',
headers={'Authorization': 'Bearer $AFRIROUTE_API_KEY'},
json={
'to': '+251911234567',
'type': 'interactive',
'interactive': {
'type': 'button',
'body': {'text': 'Confirm your appointment?'},
'action': {'buttons': [
{'type': 'reply', 'reply': {'id': 'confirm', 'title': 'Confirm'}},
{'type': 'reply', 'reply': {'id': 'reschedule', 'title': 'Reschedule'}}
]}
}
}
)

📥 Handling Replies​

A tap arrives on your webhook as an interactive message. Read the selected id:

const interactive = message.interactive;
const selectedId =
interactive.button_reply?.id || interactive.list_reply?.id;
// route on selectedId: 'support', 'track', 'sales', ...

💡 Best Practices​

  • Limit to 3 buttons; switch to a list for 4+ options.
  • Keep button titles under 20 characters and list titles under 24.
  • Use descriptive IDs (track_order, not b1) for clean routing.
  • Add a footer to set expectations (e.g. response time).
  • Always handle unknown IDs gracefully in your webhook.

⚠️ Error Handling​

CodeDescriptionSolution
131009Parameter value invalidCheck button/list structure
13104724-hour window expiredSend a template first
100Invalid interactive objectValidate against schema

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