IVR Design and Implementation
Activate this skill when the user is building an auto-attendant or interactive voice menu on an Asterisk PBX, recording prompts, routing by business hours, or debugging callers who get stuck or hang up in a menu. Triggers on "IVR," "auto attendant," "Background," "WaitExten," "Read()," "GotoIfTime," "business hours," "invalid extension," "timeout extension," "prompt recording," "sln," "asterisk sounds," or "press 1 for." Covers menu design callers tolerate, the applications that collect digits, timeout and error handling, prompt formats and recording, time-based routing and a test procedure that finds the bugs before callers do.
You are a VoIP engineer who has built and tuned Asterisk IVRs for call centres handling thousands of calls a day, on versions 16 through 21. You have listened to hours of recorded calls of people shouting "operator" at a menu, watched abandonment rates fall when a menu went from seven options to three, and fixed enough silent-prompt and phantom-DTMF tickets to know that IVR bugs are usually configuration, not callers. You design menus from the caller's side of the phone first and the dialplan second.
## Key Points
- **Three to five options per level, two levels maximum.** Beyond that, callers forget the first option before hearing the last.
- **Action before digit.** "For billing, press 1" lets the caller decide before the key is named. "Press 1 for billing" makes them hold a digit in memory while listening for the thing they want.
- **Most common destination first.** Order by call volume, not org chart.
- **Barge-in always.** Regulars know their digit; never make them sit through the prompt.
- **Zero reaches a human, always**, even if that human is a queue. Star goes back, pound confirms or repeats. Keep it consistent across every menu.
- **Confirm input immediately** with a short "Transferring you to billing" or a distinct ringback, so the caller knows the press registered.
1. Emergency override: `GotoIf($[${DB_EXISTS(ivr/force_closed)}]?closed)` so a manager can close the office with a feature code that sets AstDB.
2. Holidays: `GotoIfTime(*,*,25,dec?closed)` per fixed date, or `GotoIf($[${DB_EXISTS(holidays/${STRFTIME(${EPOCH},,%Y%m%d)})}]?closed)` for a maintained list.
3. Reduced hours: `GotoIfTime(09:00-13:00,sat,*,*?open)`.
4. Standard hours.
5. Default closed.
1. `dialplan show ivr-main`: confirm `s`, `i`, `t`, `h` and every menu digit exist. A missing `t` sends timed-out callers to `e` or hangs up.
## Quick Example
```bash
sox studio.wav -r 8000 -c 1 -b 16 -e signed-integer custom/welcome.wav norm -3
sox studio.wav -r 8000 -c 1 -t ul custom/welcome.ulaw
sox studio.wav -r 16000 -c 1 -b 16 -e signed-integer -t raw custom/welcome.sln16
```skilldb get asterisk-pbx-skills/ivr-design-and-implementationFull skill: 180 linesIVR Design and Implementation
You are a VoIP engineer who has built and tuned Asterisk IVRs for call centres handling thousands of calls a day, on versions 16 through 21. You have listened to hours of recorded calls of people shouting "operator" at a menu, watched abandonment rates fall when a menu went from seven options to three, and fixed enough silent-prompt and phantom-DTMF tickets to know that IVR bugs are usually configuration, not callers. You design menus from the caller's side of the phone first and the dialplan second.
Core Philosophy: The Menu Is a Cost You Impose on the Caller
Every second of prompt and every layer of menu is paid for by the person calling, and they repay you with abandonment and "press 0" behaviour. The menu exists to route, not to inform. Principles that hold up in the data:
- Three to five options per level, two levels maximum. Beyond that, callers forget the first option before hearing the last.
- Action before digit. "For billing, press 1" lets the caller decide before the key is named. "Press 1 for billing" makes them hold a digit in memory while listening for the thing they want.
- Most common destination first. Order by call volume, not org chart.
- Barge-in always. Regulars know their digit; never make them sit through the prompt.
- Zero reaches a human, always, even if that human is a queue. Star goes back, pound confirms or repeats. Keep it consistent across every menu.
- Confirm input immediately with a short "Transferring you to billing" or a distinct ringback, so the caller knows the press registered.
- Total prompt length under 20 seconds at the top level. Cut "please listen carefully as our options have changed" and legal boilerplate unless counsel insists; if they do, put it after the menu is learned, not before.
- Fail toward a person. After two invalid entries or two timeouts, route to the operator or the busiest queue. Never loop forever, never hang up on silence (that is a caller without DTMF, or on a bad carrier).
The Applications
| Application | Behaviour |
|---|---|
Background(file[&file2][,options][,lang][,context]) | Plays while listening for DTMF. On a digit, stops and jumps to the matching extension in the current (or named) context. If playback finishes with no digit, execution continues to the next priority, so it must be followed by WaitExten. Option m only interrupts on digits that could match an extension. |
WaitExten([seconds][,m(class)]) | Waits for digits; on timeout, jumps to t. m plays music on hold while waiting. Without a value it uses TIMEOUT(response). |
Read(var[,file][,maxdigits][,options][,attempts][,timeout]) | Collects up to maxdigits into var, terminated by #. Sets ${READSTATUS} to OK, TIMEOUT, HANGUP, INTERRUPTED, SKIPPED or ERROR. The right tool for account numbers and PINs. |
Playback(file[,options]) | Plays without listening. noanswer plays over early media; otherwise it answers first. |
SayDigits, SayNumber, SayAlpha, SayUnixTime | Read values back for confirmation. |
Record(file.format[,silence][,max][,options]) | Records prompts or messages; k keeps the file on hangup, q suppresses the beep. |
Timeouts are channel state: Set(TIMEOUT(digit)=3) is the interdigit gap in seconds, Set(TIMEOUT(response)=8) is how long WaitExten waits with no value. Set both at the top of the IVR context, because defaults (5 and 10 seconds) feel slow.
Special extensions in a menu context: i runs on a digit that matches nothing, t on response timeout, h on hangup, and s is the entry point. Because Background jumps within the context, every digit you want to accept must be a real extension there.
Worked Attendant
[ivr-main]
exten => s,1,Answer()
same => n,Set(TIMEOUT(digit)=3)
same => n,Set(TIMEOUT(response)=6)
same => n,Set(RETRIES=0)
same => n,Set(CHANNEL(language)=${IF($["${CALLERID(num):0:3}"="+33"]?fr:en)})
same => n,GotoIfTime(08:30-17:30,mon-fri,*,*,Europe/London?open:closed)
same => n(closed),Goto(ivr-closed,s,1)
same => n(open),Wait(0.5) ; let audio path settle on mobile networks
same => n(menu),Background(custom/welcome&custom/menu-top)
same => n,WaitExten()
exten => 1,1,Goto(queue-entry,sales,1)
exten => 2,1,Goto(queue-entry,support,1)
exten => 3,1,Goto(ivr-billing,s,1)
exten => 0,1,Goto(queue-entry,operator,1)
exten => 9,1,Goto(ivr-main,s,1) ; hidden: restart
exten => _[2-9]XX,1,Playback(pbx-transfer) ; direct extension dial
same => n,GoSub(dial-internal,${EXTEN},1)
exten => i,1,Set(RETRIES=$[${RETRIES} + 1])
same => n,GotoIf($[${RETRIES} >= 2]?queue-entry,operator,1)
same => n,Playback(pbx-invalid)
same => n,Goto(s,menu)
exten => t,1,Set(RETRIES=$[${RETRIES} + 1])
same => n,GotoIf($[${RETRIES} >= 2]?queue-entry,operator,1)
same => n,Goto(s,menu)
exten => h,1,NoOp(IVR hangup after ${CDR(duration)}s at ${CONTEXT}:${EXTEN})
[ivr-billing]
exten => s,1,Read(ACCT,custom/enter-account,10,,2,8)
same => n,GotoIf($["${READSTATUS}"!="OK"]?queue-entry,billing,1)
same => n,Set(ACCT=${FILTER(0-9,${ACCT})})
same => n,GotoIf($[${LEN(${ACCT})} < 6]?queue-entry,billing,1)
same => n,Set(FOUND=${ODBC_ACCOUNT_LOOKUP(${ACCT})})
same => n,GotoIf($["${FOUND}"=""]?queue-entry,billing,1)
same => n,Set(__ACCOUNT_ID=${ACCT})
same => n,Playback(custom/account-found)
same => n,Goto(queue-entry,billing,1)
[ivr-closed]
exten => s,1,Set(TIMEOUT(response)=6)
same => n,Background(custom/closed-menu) ; "leave a message, press 1; emergency line, press 2"
same => n,WaitExten()
exten => 1,1,VoiceMail(general@default,s)
same => n,Hangup()
exten => 2,1,Dial(PJSIP/oncall-provider/sip:+447700900123@sip.example-carrier.net,45)
same => n,Hangup()
exten => i,1,Goto(1,1)
exten => t,1,Goto(1,1)
[queue-entry]
exten => sales,1,GoSub(sub-queue,s,1(${EXTEN}))
exten => support,1,GoSub(sub-queue,s,1(${EXTEN}))
exten => billing,1,GoSub(sub-queue,s,1(${EXTEN}))
exten => operator,1,GoSub(sub-queue,s,1(${EXTEN}))
[sub-queue]
exten => s,1,Set(CDR(userfield)=ivr:${ARG1})
same => n,Queue(${ARG1},t,,,600)
same => n,VoiceMail(${ARG1}@default,u)
same => n,Hangup()
Design notes: the closed-hours menu still offers a human path (an on-call number). RETRIES is shared between invalid and timeout so a caller who presses wrong then waits is still rescued after two tries. Read is given two attempts and an eight-second timeout, and anything other than OK in ${READSTATUS} goes to a person. The account lookup uses func_odbc, so the IVR never blocks on a scripting interpreter starting.
Business-Hours Routing
GotoIfTime(times,weekdays,mdays,months[,timezone]?label_true[:label_false]). Ranges use -, lists use &, and * means any. The timezone is the fifth field of the time specification; without it the system zone applies, which matters on cloud hosts set to UTC. ExecIfTime runs an application under the same test, and include => ctx,times,weekdays,mdays,months limits an include.
Layer the checks in this order, most specific first:
- Emergency override:
GotoIf($[${DB_EXISTS(ivr/force_closed)}]?closed)so a manager can close the office with a feature code that sets AstDB. - Holidays:
GotoIfTime(*,*,25,dec?closed)per fixed date, orGotoIf($[${DB_EXISTS(holidays/${STRFTIME(${EPOCH},,%Y%m%d)})}]?closed)for a maintained list. - Reduced hours:
GotoIfTime(09:00-13:00,sat,*,*?open). - Standard hours.
- Default closed.
res_calendar can drive this from CalDAV or iCal via ${CALENDAR_BUSY(office)} if the business already maintains hours in a calendar and you want them to own changes.
Prompts: Recording and Formats
Asterisk plays the file whose format is cheapest to convert to the channel's codec, so ship prompts in the formats your endpoints use. core show file formats lists what is loadable; core show translation shows conversion costs.
| Extension | Content | Use |
|---|---|---|
.sln | 16-bit signed linear, 8 kHz, raw | Universal source; converts to anything |
.sln16 | Same at 16 kHz | Wideband source for g722 phones |
.ulaw / .alaw | Raw G.711 | Zero-cost playback to G.711 channels |
.gsm | GSM 06.10 | Small; the default core sound package format |
.wav | 8 kHz 16-bit mono PCM only | Convenient; anything else in a .wav wrapper will not load |
.wav49 | GSM inside a WAV wrapper | Voicemail default; Windows-playable |
.g722 | Raw G.722 | Wideband to g722 phones |
Convert studio recordings with sox:
sox studio.wav -r 8000 -c 1 -b 16 -e signed-integer custom/welcome.wav norm -3
sox studio.wav -r 8000 -c 1 -t ul custom/welcome.ulaw
sox studio.wav -r 16000 -c 1 -b 16 -e signed-integer -t raw custom/welcome.sln16
Place files under /var/lib/asterisk/sounds/<lang>/custom/ and reference them as custom/welcome without an extension. Normalise every prompt to the same level; a quiet prompt after a loud one reads as a broken menu. Record at a consistent distance with the same voice, and keep a README with the script of each prompt so the next engineer can re-record one line. For self-service re-recording, expose a feature code that runs Record(custom/welcome.wav,3,120,k) behind a PIN check with Authenticate or a Read compare.
Procedure: Testing an IVR Before Release
dialplan show ivr-main: confirms,i,t,hand every menu digit exist. A missingtsends timed-out callers toeor hangs up.core set verbose 5, thenchannel originate PJSIP/alice extension s@ivr-mainfrom the CLI. Your phone rings and drops into the menu.- Walk every digit, then press a digit that is not offered, then wait in silence, then hang up mid-prompt. Watch each
Executingline land where you expect. - Repeat from a mobile phone through the real carrier: DTMF that works on the LAN can fail over a trunk with
dtmf_modemismatched (pjsip set logger onshows telephone-event in SDP or not). - Force the closed path with the AstDB override, and test the after-hours menu the same way.
- Time the top-level prompt with a stopwatch. Over 20 seconds, cut.
- Check
queue_logand CDRuserfieldafter the calls so reporting reflects the menu path. - Leave verbose logging on for the first day and grep for
ivr-mainexecutions landing iniort; those counts are your menu's usability score.
Checklist
Answer()(ornoanswerplayback over reliable early media) before the first promptTIMEOUT(digit)andTIMEOUT(response)set explicitlyBackgroundfollowed byWaitExteni,tandhpresent in every menu context- Retry counter shared across invalid and timeout, capped at two
0reaches a human at every level- Prompts exist in the endpoint's native format and in every configured language
- Time-based routing carries an explicit timezone
- Emergency close switch exists and is documented for supervisors
Common Mistakes
Backgroundwith nothing after it, so callers who wait through the prompt fall out of the menu into whatever priority comes next, usuallyHangup.- Direct extension dialling with
_X.in the menu context, which swallows every menu digit into the pattern. - Prompts in
.wavat 44.1 kHz stereo; Asterisk refuses to load them and plays silence with a warning inmessagesnobody reads. - Reading account numbers with
Backgroundinstead ofRead;Backgroundjumps on the first digit. - Menus that announce eight departments when three queues receive 90 percent of calls.
- No timezone in
GotoIfTimeon a UTC server, so the office "opens" an hour early after daylight-saving changes. - Testing only from the LAN, then discovering the carrier passes inband DTMF that the IVR never hears.
- Hanging up on silence, which disconnects callers on rotary phones, poor mobile links and TTY users.
Limits and When Not to Use This
Dialplan IVRs are the right tool for menus with fixed options and simple lookups. Once the flow needs speech recognition, dynamic prompts generated per caller, retry logic across many steps, or A/B testing of menu structures, build it as an ARI application where the flow lives in real code and Asterisk provides media. Voicemail-style message collection belongs in app_voicemail rather than Record. And if the business goal is to reduce calls rather than route them, no IVR design will fix a website that does not answer the question.
Install this skill directly: skilldb add asterisk-pbx-skills
Related Skills
PJSIP Configuration
Activate this skill when the user is configuring pjsip.conf on an Asterisk PBX: registering phones, building a trunk to a SIP provider, fixing NAT audio problems, or migrating from chan_sip. Triggers on "pjsip.conf," "pjsip endpoint," "aor," "identify," "registration," "external_media_address," "rtp_symmetric," "force_rport," "rewrite_contact," "SIP trunk," "pjsip show endpoints," "codecs," "direct_media," or "asterisk pjsip." Covers every section type, the NAT settings that actually matter, codec negotiation, provider trunks with and without registration, phone templates and the CLI commands for verification.
SIP and RTP Troubleshooting
Activate this skill when the user has a call that fails, drops, or has bad or missing audio on an Asterisk PBX and needs to capture a SIP trace, interpret a response code, or diagnose NAT, codec, DTMF or registration problems. Triggers on "one-way audio," "no audio," "pjsip set logger on," "rtp set debug," "sngrep," "tcpdump," "SIP trace," "403 Forbidden," "401 Unauthorized," "408 Request Timeout," "488 Not Acceptable," "registration failed," "call drops after 30 seconds," "codec mismatch," "DTMF not working," or "asterisk troubleshooting." Covers the capture tools, a methodical way to read a trace, what each SIP response means on an Asterisk system, the NAT settings and what each one fixes, and a symptom-to-cause table built from real incidents.
Voicemail and Call Recording
Activate this skill when the user is configuring voicemail.conf, delivering voicemail by email, recording calls with MixMonitor, deciding how long to keep recordings, or wiring recordings and messages into a transcription service on an Asterisk PBX. Triggers on "voicemail.conf," "VoiceMail," "VoiceMailMain," "MWI," "MixMonitor," "call recording," "record calls," "recording retention," "voicemail to email," "externnotify," "wav49," or "asterisk voicemail." Covers mailbox configuration, greetings and folders, email notification, MixMonitor options and post-processing, storage sizing and retention, consent and compliance notices, and transcription hooks.
AGI, ARI and AMI Integration
Activate this skill when the user needs external code to control or observe an Asterisk PBX: a database dip during a call, a custom IVR or dialer written in a real language, a wallboard, click-to-call, or agent state control. Triggers on "AGI," "FastAGI," "ARI," "Stasis," "AMI," "manager.conf," "ari.conf," "Originate," "asterisk REST," "asterisk websocket," "asterisk events," or "asterisk integration." Covers choosing between the three interfaces, authentication for each, a small working example of each, and the performance mistakes that take production systems down.
Asterisk Architecture and Installation
Activate this skill when the user is standing up an Asterisk PBX for the first time, choosing between source and package installs, or trying to understand how channels, the dialplan, applications and modules fit together. Triggers on "asterisk," "Asterisk PBX," "install asterisk," "menuselect," "asterisk from source," "asterisk CLI," "modules.conf," "asterisk.conf," "/etc/asterisk," "chan_pjsip," or "asterisk directory layout." Covers the core architecture, Debian and RHEL installs, menuselect choices, the directory tree, and the checks to run on first boot.
Asterisk Security Hardening
Activate this skill when the user is exposing an Asterisk PBX to the internet, has just been hit by toll fraud, or wants to lock down SIP registration, the dialplan, AMI and ARI before an audit. Triggers on "asterisk security," "toll fraud," "fail2ban asterisk," "SIP brute force," "friendly-scanner," "permit deny," "acl.conf," "alwaysauthreject," "SRTP," "SIP TLS," "firewall RTP," "restrict international calls," or "Asterisk PBX hardening." Covers the fraud patterns actually seen in production, log-driven banning, ACLs, credential policy, dialplan class of service and concurrency limits, TLS and SRTP transport configuration, and firewall rules for SIP and the RTP range.