Building a Chatbot Flow
A good chatbot resolves common requests instantly and hands off gracefully when it can't. This guide covers flow design, state, and the human fallback that keeps customers from getting stuck.
🚀 Quick Start
curl -X POST https://api.afriroute.ai/api/v1/whatsapp/send \
-H "Authorization: Bearer $AFRIROUTE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "+254712345678",
"type": "interactive",
"interactive": {
"type": "button",
"body": "Hi! What do you need?",
"buttons": [
{"id": "balance", "title": "Balance"},
{"id": "pay", "title": "Pay bill"},
{"id": "agent", "title": "Agent"}
]
}
}'
🧭 Flow Design
Model the bot as a small state machine. Each state knows what it expects and where each input leads.
| State | Expects | Next |
|---|---|---|
welcome | button id | balance / pay / handoff |
pay_amount | number | pay_confirm |
pay_confirm | yes/no | done / welcome |
handoff | — | human queue |
💾 Managing State
Store per-user state keyed by phone number with a TTL so abandoned sessions expire.
import time
def get_state(phone): return store.get(f"chat:{phone}")
def set_state(phone, state, data=None):
store.set(f"chat:{phone}", {"state": state, "data": data or {}, "ts": time.time()}, ttl=1800)
async function handleMessage(from, text) {
const session = (await getState(from)) || { state: 'welcome' };
switch (session.state) {
case 'welcome': return handleWelcome(from, text);
case 'pay_amount': return handleAmount(from, text, session);
default: return sendWelcome(from);
}
}
🔀 Fallback to Human
Never trap the user. Trigger a hand-off on any of these signals:
- User taps an "Agent" button.
- The bot fails to understand the same input twice.
- The user types "help", "agent", or "human".
- A timeout with no resolution.
let misunderstood = session.data.misses || 0;
if (!matchedIntent) {
misunderstood += 1;
if (misunderstood >= 2) return routeToHuman(from);
await setState(from, session.state, { ...session.data, misses: misunderstood });
}
When handing off, pass conversation context (last intent, order ID) to the agent so the customer doesn't repeat themselves.
💡 Best Practices
- Keep menus shallow — 3 buttons or a single list, not nested trees.
- Always offer "Main menu" and "Agent" at every step.
- Expire idle sessions (~30 min) and greet returning users fresh.
- Confirm before actions that move money or change data.
- Log every transition for debugging and analytics.
- Degrade to SMS when WhatsApp delivery fails.
⚠️ Common Pitfalls
- Storing state in memory — it's lost on restart and across instances. Use a shared store.
- Free-text parsing when buttons would do — buttons remove ambiguity.
- Silent dead ends where unknown input gets no response.
📚 Related Resources
Last Updated: May 2026