Skip to main content

Chapter 21: IVR Menus

The IVR menu system lets you define prompt-driven digit-collection menus in XML, load them through mod_dptools, and invoke them from the dialplan with the ivr application. Each menu plays a greeting, collects one or more digits, and dispatches the caller to an action based on what was pressed. Menus can be nested arbitrarily: a digit can launch a submenu, return the caller to the top of the current menu, or back up one level in the stack.

IVR Menu Model

An IVR menu is a named XML element that specifies:

  • One or two greeting audio files or TTS phrases to play to the caller.
  • Timing parameters that control how long to wait for input and how many failures or timeouts to tolerate.
  • A set of <entry> elements that map a digit string (or a regular expression) to a named action.

All menus in a FreeSWITCH installation live in a shared namespace. A menu references a submenu by name; the engine resolves the name at run time from the same loaded stack. The first menu defined in a file is treated as the top of the stack for that group.

The engine collects digits until digit-len digits have been received or no digit arrives within inter-digit-timeout milliseconds after the first digit. It then walks the <entry> list for a match. If no match is found, invalid-sound plays and the failure counter increments. If no digit arrives at all within timeout milliseconds, the timeout counter increments. When either counter reaches its maximum, the menu exits.


Configuration File

ivr.conf.xml is the module configuration file loaded by mod_dptools. Its only job is to aggregate menu files via an X-PRE-PROCESS include directive.

<!-- conf/autoload_configs/ivr.conf.xml -->
<configuration name="ivr.conf" description="IVR menus">
<menus>
<X-PRE-PROCESS cmd="include" data="../ivr_menus/*.xml"/>
</menus>
</configuration>

Every *.xml file under conf/ivr_menus/ is loaded at startup. Each of those files contains one or more <menu> elements wrapped in an <include> root element.


A menu file holds one <include> wrapper containing one or more <menu> elements. The name attribute on each <menu> is the identifier used both by the ivr dialplan application and by menu-sub entries.

<include>
<menu name="main_menu"
greet-long="phrase:main_menu_greeting"
greet-short="phrase:main_menu_greeting_short"
invalid-sound="ivr/ivr-that_was_an_invalid_entry.wav"
exit-sound="voicemail/vm-goodbye.wav"
timeout="10000"
inter-digit-timeout="2000"
max-failures="3"
max-timeouts="3"
digit-len="1">

<entry action="menu-exec-app" digits="1" param="transfer 1000 XML default"/>
<entry action="menu-sub" digits="2" param="billing_menu"/>
<entry action="menu-top" digits="9"/>
<entry action="menu-exit" digits="*"/>
</menu>
</include>
AttributePurposeAccepted ValuesDefault
nameUnique identifier for this menu. Required.Any string(none)
greet-longAudio or TTS played on the first entry into the menu.File path or phrase: / say: prefix(none)
greet-shortAudio or TTS played on re-entry (after returning from a submenu or looping).File path or phrase: / say: prefix(none)
invalid-soundAudio or TTS played when the caller presses digits that do not match any entry.File path or phrase: / say: prefix(none)
exit-soundAudio or TTS played when the menu exits normally (max failures, max timeouts, or menu-exit).File path or phrase: / say: prefix(none)
transfer-soundAudio or TTS played before transferring the caller.File path or phrase: / say: prefix(none)
timeoutMilliseconds to wait for the first digit before incrementing the timeout counter.Integer (ms)10000
inter-digit-timeoutMilliseconds to wait between digits after the first digit has been received.Integer (ms)Half of timeout when timeout is explicitly set; 0 (no inter-digit timeout) when both are omitted
max-failuresNumber of invalid-digit events tolerated before the menu exits.Integer3
max-timeoutsNumber of timeout events tolerated before the menu exits. If omitted, inherits the value of max-failures.IntegerValue of max-failures
digit-lenMaximum number of digits to collect before matching. When set to 0, the engine automatically grows digit-len to the length of the longest literal digits value bound in the menu's entries.Integer0
confirm-macroPhrase macro name to play back the collected digits for confirmation before executing the matched action.Phrase macro name(none)
confirm-keyDigit the caller presses to confirm. Used with confirm-macro.Single digit string(none)
confirm-attemptsNumber of confirmation attempts allowed.Integer3
tts-engineTTS engine to use when sound values begin with say:.Engine name (for example flite)(none)
tts-voiceTTS voice to use with tts-engine.Voice name (for example rms)(none)
pinIf set, the caller must enter this PIN before the menu greeting plays.Digit string(none)
pin-fileAudio prompt played before PIN entry.File pathivr/ivr-please_enter_pin_followed_by_pound.wav
bad-pin-fileAudio played when the caller enters a wrong PIN.File pathconference/conf-bad-pin.wav
exec-on-max-failuresDialplan application string executed when max-failures is reached.app arg string(none)
exec-on-max-timeoutsDialplan application string executed when max-timeouts is reached.app arg string(none)

Sound and TTS Prefixes

All sound attributes (greet-long, greet-short, invalid-sound, exit-sound, transfer-sound) accept three forms:

FormExampleBehavior
File pathivr/ivr-that_was_an_invalid_entry.wavPlays the audio file relative to the sounds base directory.
phrase: prefixphrase:main_menu_greetingExecutes the named phrase macro.
say: prefixsay:Press 1 for salesSynthesizes speech using tts-engine and tts-voice.

PIN Protection

When pin is set on a menu, the engine prompts the caller with pin-file before playing the greeting. If the caller enters the correct PIN, the menu proceeds normally. If the PIN does not match, bad-pin-file plays. The engine allows exactly 3 PIN attempts; this value is hardcoded and is not controlled by any <menu> attribute.

<menu name="secure_menu"
greet-long="phrase:secure_menu_greeting"
greet-short="phrase:secure_menu_greeting_short"
invalid-sound="ivr/ivr-that_was_an_invalid_entry.wav"
exit-sound="voicemail/vm-goodbye.wav"
timeout="10000"
max-failures="3"
max-timeouts="3"
digit-len="1"
pin="1234"
pin-file="ivr/ivr-please_enter_pin_followed_by_pound.wav"
bad-pin-file="conference/conf-bad-pin.wav">

<entry action="menu-exec-app" digits="1" param="transfer 2000 XML default"/>
<entry action="menu-exit" digits="*"/>
</menu>

Callbacks on Failure and Timeout

exec-on-max-failures and exec-on-max-timeouts each accept a dialplan application invocation in the form application argument. When the respective counter is reached the engine executes the application before exiting the menu. This is the correct hook for sending the caller to a voicemail extension or a live-agent queue when they fail to interact with the menu.

<menu name="main_menu"
greet-long="phrase:main_menu_greeting"
greet-short="phrase:main_menu_greeting_short"
invalid-sound="ivr/ivr-that_was_an_invalid_entry.wav"
exit-sound="voicemail/vm-goodbye.wav"
timeout="10000"
max-failures="3"
max-timeouts="3"
digit-len="1"
exec-on-max-failures="transfer 0 XML default"
exec-on-max-timeouts="transfer 0 XML default">

<entry action="menu-exec-app" digits="1" param="transfer 1000 XML default"/>
</menu>

Entries: the <entry> Element

Each <entry> element inside a <menu> maps one digit string or regular expression to one action. Attributes:

  • action - the action name (required)
  • digits - the digit string or regex to match (required)
  • param - additional argument to the action (required for actions that take one, not used by menu-top or menu-back)

<entry> Action Reference

ActionEffect
menu-exec-appExecutes a dialplan application with the value of param as its argument. The call leaves the menu. Example: param="transfer 1000 XML default".
menu-subEnters the named submenu specified by param. The current menu is pushed onto the stack.
menu-topReturns to the top of the menu stack (the first menu defined in the current group), replaying greet-short.
menu-backReturns to the previous menu in the stack (one level up), replaying that menu's greet-short.
menu-play-soundPlays the audio or TTS value in param without leaving the menu.
menu-exitExits the menu immediately, playing exit-sound.

Digit Patterns and Regular Expressions

The digits attribute accepts either a literal string or a PCRE regular expression enclosed in / delimiters. When a regex is used, any capture groups are exposed as $1, $2, and so on, within the param value of a menu-exec-app entry. This enables variable-length extension dialing directly from an IVR menu.

<!-- Literal: caller presses exactly "1" -->
<entry action="menu-exec-app" digits="1" param="transfer 1000 XML default"/>

<!-- Regex: matches any 4-digit extension in the 1000-1099 range -->
<entry action="menu-exec-app" digits="/^(10[0-9][0-9])$/" param="transfer $1 XML default"/>

Multiple <entry> elements may use different regex patterns. Entries are evaluated in document order; the first match wins.


Nesting Submenus

A submenu is an ordinary <menu> element referenced by name from a menu-sub entry. All menus referenced within a single file (or across files loaded into the same <menus> block) share the same namespace. The engine resolves submenu names at runtime from the loaded stack.

Best practice is to define the top-level menu first in a file and all submenus below it. The menu-back action navigates up one level; menu-top returns to the first menu defined for the current call invocation.

<include>
<!-- Top-level menu -->
<menu name="company_ivr"
greet-long="phrase:company_ivr_main"
greet-short="phrase:company_ivr_main_short"
invalid-sound="ivr/ivr-that_was_an_invalid_entry.wav"
exit-sound="voicemail/vm-goodbye.wav"
timeout="10000"
inter-digit-timeout="2000"
max-failures="3"
max-timeouts="3"
digit-len="1">

<entry action="menu-sub" digits="1" param="sales_menu"/>
<entry action="menu-sub" digits="2" param="support_menu"/>
<entry action="menu-top" digits="9"/>
<entry action="menu-exit" digits="*"/>
</menu>

<!-- Submenu -->
<menu name="sales_menu"
greet-long="phrase:sales_menu_greeting"
greet-short="phrase:sales_menu_greeting_short"
invalid-sound="ivr/ivr-that_was_an_invalid_entry.wav"
exit-sound="voicemail/vm-goodbye.wav"
timeout="15000"
max-failures="3"
max-timeouts="3"
digit-len="1">

<entry action="menu-exec-app" digits="1" param="transfer 2001 XML default"/>
<entry action="menu-exec-app" digits="2" param="transfer 2002 XML default"/>
<entry action="menu-back" digits="*"/>
</menu>
</include>

In this example, pressing * in sales_menu invokes menu-back, returning the caller to company_ivr and replaying its short greeting. Pressing 9 from company_ivr invokes menu-top, which restarts company_ivr from the top.


Invoking a Menu from the Dialplan

The ivr dialplan application takes a single argument: the name of the top-level menu to enter. The call must already be answered before ivr is invoked. The application is also summarized in the dptools reference.

<extension name="main_ivr">
<condition field="destination_number" expression="^1000$">
<action application="answer"/>
<action application="sleep" data="1000"/>
<action application="ivr" data="company_ivr"/>
</condition>
</extension>

The sleep before ivr gives the far end time to complete answer signaling before audio begins. The value in milliseconds depends on the carrier or endpoint; 1000 to 2000 is common.

When ivr returns, control passes to the next action in the dialplan extension. If the menu dispatched the call internally (via menu-exec-app with transfer), the transfer takes the call out of the current extension and into the target extension in the named context.


The Bundled Demo IVR

The vanilla FreeSWITCH configuration ships a working demo IVR reachable at extension 5000 in the default dialplan context. The dialplan extension in conf/dialplan/default.xml is:

<extension name="ivr_demo">
<condition field="destination_number" expression="^5000$">
<action application="answer"/>
<action application="sleep" data="2000"/>
<action application="ivr" data="demo_ivr"/>
</condition>
</extension>

The demo_ivr menu is defined in conf/ivr_menus/demo_ivr.xml. Its full definition:

<include>
<!-- demo IVR, Main Menu -->
<menu name="demo_ivr"
greet-long="phrase:demo_ivr_main_menu"
greet-short="phrase:demo_ivr_main_menu_short"
invalid-sound="ivr/ivr-that_was_an_invalid_entry.wav"
exit-sound="voicemail/vm-goodbye.wav"
confirm-macro=""
confirm-key=""
tts-engine="flite"
tts-voice="rms"
confirm-attempts="3"
timeout="10000"
inter-digit-timeout="2000"
max-failures="3"
max-timeouts="3"
digit-len="4">

<entry action="menu-exec-app" digits="1" param="bridge sofia/$${domain}/888@conference.freeswitch.org"/>
<entry action="menu-exec-app" digits="2" param="transfer 9196 XML default"/>
<entry action="menu-exec-app" digits="3" param="transfer 9664 XML default"/>
<entry action="menu-exec-app" digits="4" param="transfer 9191 XML default"/>
<entry action="menu-exec-app" digits="5" param="transfer 1234*256 enum"/>
<entry action="menu-sub" digits="6" param="demo_ivr_submenu"/>
<entry action="menu-exec-app" digits="/^(10[01][0-9])$/" param="transfer $1 XML features"/>
<entry action="menu-top" digits="9"/>
</menu>

<!-- Demo IVR, Sub Menu -->
<menu name="demo_ivr_submenu"
greet-long="phrase:demo_ivr_sub_menu"
greet-short="phrase:demo_ivr_sub_menu_short"
invalid-sound="ivr/ivr-that_was_an_invalid_entry.wav"
exit-sound="voicemail/vm-goodbye.wav"
timeout="15000"
max-failures="3"
max-timeouts="3">

<entry action="menu-top" digits="*"/>
</menu>
</include>

The digit map for demo_ivr:

DigitAction
1Bridges the caller to the public FreeSWITCH conference at 888@conference.freeswitch.org.
2Transfers to extension 9196 (echo test).
3Transfers to extension 9664 (music on hold).
4Transfers to extension 9191.
5Transfers to extension 1234*256 in the enum context.
6Enters demo_ivr_submenu.
9Returns to the top of demo_ivr.
1000-1019Transfers any 4-digit match of /^(10[01][0-9])$/ to that extension in the features context.

The digit-len of 4 means the engine waits for up to 4 digits before matching, which is why single-digit entries like 1 through 9 still work: after the caller presses one digit, inter-digit-timeout (2000 ms) elapses with no further input, and the engine matches on the 1-digit buffer.

demo_ivr_submenu plays its greeting and waits. Pressing * fires menu-top, returning the caller to the top of demo_ivr.

The vanilla configuration also ships conf/ivr_menus/new_demo_ivr.xml, which defines new_demo_ivr and several additional submenus following the same structural pattern. No default dialplan extension invokes new_demo_ivr; it serves as a reference example for multi-submenu layout.