Skip to main content

Designing Voice IVR Menus

A well-designed IVR gets callers to the right place in seconds. This guide covers menu structure, prompt writing, choosing DTMF versus speech, and handling the inevitable mistakes.

🚀 Quick Start​

curl -X POST https://api.afriroute.ai/api/v1/voice/calls \
-H "Authorization: Bearer $AFRIROUTE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "+254712345678",
"from": "+254700000000",
"flow": {
"say": "Welcome to Acme. Press 1 for sales, 2 for support.",
"gather": { "input": "dtmf", "num_digits": 1, "action_url": "https://example.com/ivr" }
}
}'

🧭 Menu Structure​

Keep menus shallow and option counts low. Callers can't hold more than a few choices in memory.

RuleTarget
Options per menu4 or fewer
Menu depth3 levels max
Time to first menuUnder 5 seconds
Repeat optionAlways include

✍️ Writing Prompts​

  • State the action before the key: "For support, press 2" — not "Press 2 for support."
  • Put the most-used option first.
  • Keep each prompt under 7 seconds.
  • Always offer "press 0 for an operator" and "press star to repeat".
{
"say": "For account balance, press 1. To make a payment, press 2. To speak to an agent, press 0.",
"gather": { "input": "dtmf", "num_digits": 1, "timeout": 5 }
}

🔢 DTMF vs Speech​

InputBest forWatch out for
DTMF (keypad)Menus, PINs, amountsLimited to numbers
SpeechOpen questions, names, addressesAccents, noise, cost

For African deployments with many languages and noisy environments, DTMF is more reliable for menus. Reserve speech for open-ended capture and pair it with confidence checks — see Speech Recognition.

const flow = {
say: 'Say the city you are calling about, or press 0 for an agent.',
gather: { input: 'speech dtmf', language: 'en-KE', action_url: 'https://example.com/ivr/city' }
};

⚠️ Error Handling​

Handle the three failure modes explicitly: no input, invalid input, and repeated failure.

def on_gather(req):
if req['digits'] == '': # timeout / no input
return reprompt(attempt=req['attempt'])
if req['digits'] not in VALID: # invalid key
return invalid_prompt(req['attempt'])
return route(req['digits'])

def reprompt(attempt):
if attempt >= 2:
return {"say": "Transferring you to an agent.", "dial": "+254700000000"}
return {"say": "I didn't catch that. Please try again.", "gather": MENU}

💡 Best Practices​

  • Cap retries at 2, then fall back to a human.
  • Confirm critical inputs (amounts, account numbers) by reading them back.
  • Let callers interrupt prompts (barge-in) once they know the menu.
  • Localize prompts by caller country or a language-select first step.
  • Log path drop-off to find confusing menus.

Last Updated: May 2026