Skip to main content
Technology & EngineeringAsterisk PBX180 lines

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.

Quick Summary26 lines
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 lines
Paste into your CLAUDE.md or agent config

IVR 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

ApplicationBehaviour
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, SayUnixTimeRead 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:

  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.

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.

ExtensionContentUse
.sln16-bit signed linear, 8 kHz, rawUniversal source; converts to anything
.sln16Same at 16 kHzWideband source for g722 phones
.ulaw / .alawRaw G.711Zero-cost playback to G.711 channels
.gsmGSM 06.10Small; the default core sound package format
.wav8 kHz 16-bit mono PCM onlyConvenient; anything else in a .wav wrapper will not load
.wav49GSM inside a WAV wrapperVoicemail default; Windows-playable
.g722Raw G.722Wideband 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

  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.
  2. core set verbose 5, then channel originate PJSIP/alice extension s@ivr-main from the CLI. Your phone rings and drops into the menu.
  3. Walk every digit, then press a digit that is not offered, then wait in silence, then hang up mid-prompt. Watch each Executing line land where you expect.
  4. Repeat from a mobile phone through the real carrier: DTMF that works on the LAN can fail over a trunk with dtmf_mode mismatched (pjsip set logger on shows telephone-event in SDP or not).
  5. Force the closed path with the AstDB override, and test the after-hours menu the same way.
  6. Time the top-level prompt with a stopwatch. Over 20 seconds, cut.
  7. Check queue_log and CDR userfield after the calls so reporting reflects the menu path.
  8. Leave verbose logging on for the first day and grep for ivr-main executions landing in i or t; those counts are your menu's usability score.

Checklist

  • Answer() (or noanswer playback over reliable early media) before the first prompt
  • TIMEOUT(digit) and TIMEOUT(response) set explicitly
  • Background followed by WaitExten
  • i, t and h present in every menu context
  • Retry counter shared across invalid and timeout, capped at two
  • 0 reaches 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

  • Background with nothing after it, so callers who wait through the prompt fall out of the menu into whatever priority comes next, usually Hangup.
  • Direct extension dialling with _X. in the menu context, which swallows every menu digit into the pattern.
  • Prompts in .wav at 44.1 kHz stereo; Asterisk refuses to load them and plays silence with a warning in messages nobody reads.
  • Reading account numbers with Background instead of Read; Background jumps on the first digit.
  • Menus that announce eight departments when three queues receive 90 percent of calls.
  • No timezone in GotoIfTime on 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

Get CLI access →

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.

Asterisk PBX238L

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.

Asterisk PBX201L

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.

Asterisk PBX168L

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 PBX210L

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 PBX193L

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.

Asterisk PBX250L