DEV Community

Jason Shouldice
Jason Shouldice

Posted on • Edited on • Originally published at vicistack.com

Building a VICIdial IVR That Doesn't Make Callers Hang Up

Your IVR is the first thing a customer hears when they call. If it's three seconds of silence followed by a robot voice saying "please hold," followed by four minutes of distorted hold music, you have a 40% abandonment rate and you earned it.

VICIdial's IVR system is built around a feature called Call Menus -- the admin-level configuration that controls what callers hear, which DTMF key presses route where, and how calls flow from a DID to an agent's headset. It's flexible, handles everything from 3-option auto-attendants to 6-level-deep menu trees with time-of-day routing and language selection. But it's unintuitive. The admin GUI gives you a blank form with 20+ fields, no tooltips, and documentation that assumes you already know how IVR routing works.

The Three-Link Chain

Every inbound call follows three links:

  1. DID receives the call. Your SIP carrier delivers the call to Asterisk. The dialed number gets matched against VICIdial's DID table.
  2. DID routes to a Call Menu (IVR). The caller hears your prompt and presses a key.
  3. Call Menu routes to an Inbound Group (queue). The caller enters the queue, hears hold music, and waits for an agent.

DIDs, Call Menus, and Inbound Groups are three separate objects in VICIdial. They connect like a chain. Get one link wrong and calls dead-end or route to the wrong place.

The DID: Where Everything Starts

Navigate to Admin > Inbound DID. Each DID entry tells VICIdial what to do when a call arrives for that number.

Field Purpose Example
DID Extension The phone number as delivered by carrier 15551234567
DID Route Where to send the call CALLMENU, IN_GROUP, or AGENT
Call Menu Which call menu (if CALLMENU) MAIN_IVR
In-Group Which inbound group (if IN_GROUP) SALES_QUEUE

The DID Extension must match exactly what your carrier sends. This trips up more people than any other IVR issue. Some carriers send full E.164 with country code (15551234567). Others strip it (5551234567). Some prepend a "1". Run asterisk -rx "sip set debug on", place a test call, and look at the To header in the INVITE. That exact string goes in the DID Extension field.

Building a Basic Auto-Attendant

Navigate to Admin > Call Menus > Add A New Call Menu.

Field Value Why
Menu ID MAIN_IVR Unique ID. Alphanumeric, no spaces.
Menu Name Main Auto-Attendant Descriptive name for admin.
Menu Prompt main-greeting Asterisk audio file name (no extension).
Menu Timeout 10 Seconds to wait for DTMF input.
Menu Timeout Prompt sorry-try-again What to play if no input.
Menu Invalid Prompt invalid-option What to play for unrecognized key.
Menu Repeat 3 How many replays before timeout action.

Then add DTMF options:

Key Destination Route Type
1 SALES_QUEUE IN_GROUP
2 SUPPORT_QUEUE IN_GROUP
3 BILLING_QUEUE IN_GROUP
0 OPERATOR_QUEUE IN_GROUP
t SALES_QUEUE IN_GROUP
i MAIN_IVR CALLMENU

The t option catches timeouts -- if the caller doesn't press anything after all repeats, send them to the default queue. Never leave callers in silence. The i option catches invalid input -- replay the menu.

Link the DID: Admin > Inbound DID > set DID Route to CALLMENU > select MAIN_IVR.

Call Menu Option Types

Each DTMF option can route to:

  • IN_GROUP -- Agent queue. Most common for leaf-level menu options.
  • CALLMENU -- Another Call Menu. How you build multi-level IVRs.
  • DID -- Another DID entry. Useful for forwarding to external numbers.
  • EXTENSION -- Specific Asterisk extension. Direct-to-agent, voicemail, or custom dialplan.
  • VOICEMAIL -- Directly to a voicemail box.
  • AGI -- Execute an Asterisk AGI script for custom routing logic.
  • HANGUP -- Disconnect. Use sparingly.

Menu Prompts: Single vs Multi-File

Single prompt: main-greeting -- plays main-greeting.wav from /var/lib/asterisk/sounds/.

Multi-file sequence: welcome-to-company|press-one-for-sales|press-two-for-support -- plays each file in order. Useful because you can swap individual segments without re-recording the whole greeting.

Insert silence between segments: main-greeting|silence/2|menu-options -- the silence/2 adds 2 seconds of breathing room. Small touch, sounds way more professional.

Audio Quality: The Thing That Makes or Breaks Your IVR

Bad audio is the number one thing that makes an IVR sound amateur. A tinny recording with background noise tells callers they've reached a company that doesn't care about details.

Format Requirements

  • WAV (PCM), 8000 Hz, 16-bit, mono, linear PCM
  • This is the native format for G.711 telephony
  • If you upload 44.1kHz stereo, Asterisk transcodes on every playback, wasting CPU and adding artifacts

Convert with SOX

sox original-recording.wav -r 8000 -c 1 -b 16 main-greeting.wav
Enter fullscreen mode Exit fullscreen mode

Batch conversion:

for f in /path/to/originals/*.wav; do
  sox "$f" -r 8000 -c 1 -b 16 "/var/lib/asterisk/sounds/$(basename $f)"
done
Enter fullscreen mode Exit fullscreen mode

Upload to VICIdial

Via the admin: Admin > Audio Store. Upload WAV files, VICIdial copies them to the correct directory. File name minus extension becomes the prompt name.

Via SCP:

scp main-greeting.wav root@vicidial-server:/var/lib/asterisk/sounds/
chown asterisk:asterisk /var/lib/asterisk/sounds/main-greeting.wav
chmod 644 /var/lib/asterisk/sounds/main-greeting.wav
Enter fullscreen mode Exit fullscreen mode

Recording Tips

Record all prompts in one session with the same voice talent. Mismatched voices across menu levels sound terrible. Record each option as a separate file. Include 0.5 seconds of clean silence at the start and end. Normalize volume across all files.

Time-Based Routing: Business Hours, Holidays, After-Hours

A production IVR can't play the same menu 24/7. During business hours, "Press 1 for Sales" routes to a live queue. After hours, it should route to voicemail.

Setting Up Call Times

Navigate to Admin > Call Times:

Field Value
Call Time ID BUSINESS_HOURS
Monday-Friday Start 0800
Monday-Friday End 1800
Saturday/Sunday (blank = closed)

Add holidays under Call Time Holidays. Each holiday date overrides the daily schedule.

Call Menu Time Check

Set Menu Time Check to BUSINESS_HOURS. Configure separate prompts and options for in-hours and after-hours:

Menu ID:          MAIN_IVR
Menu Time Check:  BUSINESS_HOURS

--- In-Hours ---
Prompt:           business-hours-greeting
Option 1:         IN_GROUP > SALES_QUEUE
Option 2:         IN_GROUP > SUPPORT_QUEUE
Option 3:         IN_GROUP > BILLING_QUEUE
Timeout:          IN_GROUP > SALES_QUEUE

--- After-Hours ---
Prompt:           after-hours-greeting
Option 1:         VOICEMAIL > 1000
Option 2:         EXTENSION > hours-info
Option 3:         CALLMENU > CALLBACK_MENU
Timeout:          VOICEMAIL > 1000
Enter fullscreen mode Exit fullscreen mode

One Call Menu handles both scenarios. No manual switching required.

Time Zone Warning

VICIdial uses the server's system time for Call Time evaluations. If your server is in Eastern but your business operates in Pacific, your "9 AM open" triggers at noon Eastern.

timedatectl set-timezone America/Los_Angeles
Enter fullscreen mode Exit fullscreen mode

Multi-Level IVRs: Don't Go Past Three

Chain Call Menus by setting DTMF options to route type CALLMENU pointing to another menu.

MAIN_IVR (Level 1)
+-- Press 1 > SALES_MENU (Level 2)
|   +-- Press 1 > NEW_CUSTOMERS_QUEUE
|   +-- Press 2 > EXISTING_ACCOUNTS_QUEUE
|   +-- Press 0 > MAIN_IVR (back)
+-- Press 2 > SUPPORT_MENU (Level 2)
|   +-- Press 1 > TECH_SUPPORT_QUEUE
|   +-- Press 2 > BILLING_SUPPORT_QUEUE
|   +-- Press 0 > MAIN_IVR (back)
+-- Press 3 > BILLING_QUEUE (direct, no sub-menu)
+-- Press 0 > OPERATOR_QUEUE
Enter fullscreen mode Exit fullscreen mode

Always include a "return to previous menu" option. Press 0 or Press * to go back. Without it, callers in the wrong sub-menu hang up and call back.

Three levels is the practical maximum. At four levels, callers have been pressing buttons for over a minute before reaching a human. Satisfaction drops sharply after 45 seconds of IVR navigation. If your routing needs four levels, rethink the tree. Collect the critical decision at level 1 (language or department), then route to an inbound group with skill-based routing. Let the agent handle sub-categorization.

Language Selection Pattern

LANGUAGE_SELECT (Level 1)
+-- Press 1 (or timeout) > MAIN_IVR_EN (English menus)
+-- Press 2 > MAIN_IVR_ES (Spanish menus)
Enter fullscreen mode Exit fullscreen mode

Each language gets its own Call Menus with localized prompts and potentially different inbound groups. Timeout defaults to primary language.

The DTMF Gotcha

If callers press keys but nothing happens, it's almost always a DTMF mode mismatch. VICIdial's IVR relies on Asterisk receiving DTMF events. If the carrier sends DTMF in-band (audio tones) but your trunk is set for RFC 2833, Asterisk never sees the key press.

asterisk -rx "sip show peer your-carrier" | grep -i dtmf
Enter fullscreen mode Exit fullscreen mode

Should show dtmfmode=rfc2833 for most modern carriers. If DTMF isn't working, enable verbose logging:

asterisk -rx "core set verbose 5"
Enter fullscreen mode Exit fullscreen mode

Place a test call and press keys. You should see lines like:

DTMF/SIP-xxx-1 '1' received on SIP/trunk-00000001
Enter fullscreen mode Exit fullscreen mode

If you don't see DTMF events, the issue is between the carrier and Asterisk.

Inbound Groups: Where Calls Meet Agents

Each Call Menu option eventually routes to an inbound group -- VICIdial's queue system. Create groups at Admin > Inbound Groups:

Field Value Notes
Group ID SALES_QUEUE Must match what you entered in Call Menu
Active Y Must be active to receive calls
Call Time ID 24hours or BUSINESS_HOURS Business hours for this queue
After Hours Action CALLMENU Route to after-hours menu

Agents receive calls through closer campaigns. Set Campaign Allow Inbound to Y and list the inbound groups in Closer Campaigns.

Different hold music per queue reinforces branding:

[sales-hold]
mode=files
directory=/var/lib/asterisk/moh/sales

[support-hold]
mode=files
directory=/var/lib/asterisk/moh/support
Enter fullscreen mode Exit fullscreen mode

Inbound Groups: Connecting Agents to Call Menus

Every Call Menu option eventually routes to an inbound group. Understanding how agents connect to these groups is essential.

Agents receive inbound calls through closer campaigns. The campaign needs:

  • Campaign Type: Allow inbound (or dedicated CLOSER type)
  • Allowed Inbound Groups: Which groups this campaign's agents can receive
  • Dial Method: For blended operations, agents take inbound between outbound attempts

Priority determines which agents get calls first:

  1. Agent rank within the inbound group (set per-agent, 0-9)
  2. Longest wait time among equal-rank agents
  3. Queue priority if multiple groups compete for the same agents

Hold Music Per Queue

Different hold music per queue reinforces branding and sets expectations. Create MOH classes in musiconhold.conf and reference them in each inbound group:

[sales-hold]
mode=files
directory=/var/lib/asterisk/moh/sales
sort=alpha

[support-hold]
mode=files
directory=/var/lib/asterisk/moh/support
sort=random
Enter fullscreen mode Exit fullscreen mode

Place 8kHz mono WAV files in the respective directories. A caller waiting in the support queue hearing different music than the sales queue subtly tells them they're in the right place.

Queue Position and Wait Time Announcements

Configure in each inbound group:

Field Purpose
On Hold Prompt Audio file or MOH class while waiting
Hold Time Announce "Your call will be answered in approximately 3 minutes"
Queue Position Announce "You are caller number 5 in the queue"
Announce Frequency How often to repeat (seconds)

These announcements keep callers engaged and reduce abandonment. For high-volume queues, the callback option (CALLERID_CALLBACK) is even better -- callers press a key to leave the queue and get called back when an agent is free, maintaining their position.

Asterisk Dialplan Integration for Advanced IVR

VICIdial's Call Menu system handles 90% of IVR needs through the admin GUI. For advanced scenarios -- database lookups, API-driven routing, dynamic prompts -- you drop into Asterisk's dialplan and AGI.

Custom logic goes in extensions_custom.conf -- never in extensions.conf, which VICIdial overwrites.

After-Hours Voicemail with Custom Greeting

[custom-afterhours-vm]
exten => s,1,Answer()
 same => n,Wait(1)
 same => n,Playback(afterhours-message)
 same => n,Playback(beep)
 same => n,VoiceMail(1000@default,u)
 same => n,Hangup()
Enter fullscreen mode Exit fullscreen mode

Reference from a Call Menu option by setting route type to EXTENSION and destination to custom-afterhours-vm,s,1.

Dynamic VIP Routing

[custom-vip-check]
exten => s,1,Set(CALLER=${CALLERID(num)})
 same => n,AGI(vip-lookup.agi,${CALLER})
 same => n,GotoIf($["${VIP}" = "yes"]?vip:regular)
 same => n(vip),Goto(default,8300,1)
 same => n(regular),Goto(default,8301,1)
Enter fullscreen mode Exit fullscreen mode

This checks the caller against a VIP list via AGI and routes high-value callers to a priority queue, skipping the IVR entirely.

AGI scripts (Perl, PHP, Python) live in /var/lib/asterisk/agi-bin/. Make them executable and owned by the asterisk user:

chmod 755 /var/lib/asterisk/agi-bin/vip-lookup.agi
chown asterisk:asterisk /var/lib/asterisk/agi-bin/vip-lookup.agi
Enter fullscreen mode Exit fullscreen mode

Testing the Complete IVR Flow

Before going live, test everything:

  1. Call each DID and verify it enters the correct Call Menu
  2. Test every DTMF option and verify it routes to the correct inbound group
  3. Test timeout behavior -- let the menu play through all repeats without pressing anything
  4. Test invalid input -- press a key that isn't defined
  5. Test after-hours routing -- change the Call Time temporarily or test outside business hours
  6. Test overflow -- fill the queue and verify overflow destinations work
  7. Test from a mobile phone -- DTMF works differently on some mobile carriers
  8. Test voicemail -- leave a test message, verify it arrives via email notification

Document the entire flow with a diagram showing every path a caller can take. When the person who built the IVR leaves the company (and they will), the diagram is the only thing that keeps the next person from rebuilding it from scratch.

DTMF Multi-Digit Input

Most IVRs use single-digit options, but Call Menus also support multi-digit input for extension dialing, account number lookup, or PIN verification. Set Menu Timeout appropriately -- callers need enough time between digits. 3-5 seconds per expected digit. The # key typically serves as the submit delimiter.

For account-number-based routing, use an AGI script that receives the DTMF input, performs a database lookup, and sets a channel variable the dialplan uses for routing. This lets callers enter their account number and get routed to the right department automatically -- skipping the menu entirely for known accounts.

Full Testing Checklist

Before going live with any IVR change:

  1. Call each DID and verify correct Call Menu loads
  2. Press every defined key and verify routing
  3. Press an undefined key and verify invalid-input handling
  4. Let the menu timeout without pressing anything, verify timeout routing
  5. Test after-hours routing -- change the Call Time temporarily or call outside hours
  6. Fill the target queue and verify overflow behavior
  7. Test from a mobile phone on a different carrier -- DTMF works differently
  8. Test voicemail -- leave a message, verify email notification arrives
  9. Test sub-menus -- navigate into each, verify return-to-main works
  10. Test with a colleague who doesn't know the menu -- watch where they get confused

Document the entire flow with a diagram. When the person who built the IVR leaves (and they will), the diagram is the only thing keeping the next person from rebuilding from scratch.

Common IVR Mistakes

Dead-end menu options. An option that plays information ("Our hours are 9 to 5") and then dumps the caller to silence. Always route back to the menu or to a queue after informational options.

No timeout handler. Elderly callers and callers on landlines may not hear the menu clearly. Timeout should route to a default queue, not disconnect.

Menu too long. If your greeting plus options takes longer than 30 seconds, callers zone out. Keep the main menu under 20 seconds. Put detail in sub-menus.

Mismatch between recorded options and actual routing. The greeting says "Press 3 for Billing" but option 3 actually routes to Support because someone changed the routing but didn't re-record the prompt. Audit prompts against routing quarterly.

No return-to-main option in sub-menus. Callers in the wrong sub-menu will hang up and call back. Always offer Press 0 or Press * to go back.

Testing only from the office. Your desk phone uses the same PBX as the dialer. Test from an external line -- preferably a mobile phone on a different carrier -- to verify the full SIP chain, DTMF delivery, and audio quality.

Not updating prompts when routing changes. The greeting says "Press 3 for Billing" but option 3 routes to Support because someone changed the routing and nobody re-recorded. Audit prompts against actual routing quarterly.

Ignoring mobile callers. A significant percentage of your inbound calls come from mobile phones. Mobile carriers handle DTMF differently, audio quality varies, and callers are often in noisy environments. If your IVR prompt is a wall of speech with no pauses, mobile callers miss the options. Keep prompts short, insert silence between options, and speak slowly.

Server Timezone and IVR

VICIdial uses the server's system time for Call Time evaluations. If your server is in US Eastern but your business operates in US Pacific, your "9 AM open" will actually trigger at noon Eastern. Set the server timezone correctly:

timedatectl set-timezone America/Los_Angeles
Enter fullscreen mode Exit fullscreen mode

For operations spanning multiple timezones, you have two options: set the server to your primary business timezone, or use custom AGI scripts that evaluate multiple timezones and route accordingly. Most operations set the server timezone to match their headquarters location and define Call Times relative to that.

IVR Performance Tracking

VICIdial tracks IVR traffic through optional tracking groups. Set the Tracking Group field in each Call Menu to a group ID. This lets you report on how many callers entered each menu, which options they selected, and where they dropped off.

Key metrics to watch:

  • IVR completion rate: What percentage of callers who enter the IVR actually reach an agent queue? If it's below 85%, your menu is losing people.
  • Option distribution: Which options get the most traffic? If 80% of callers press 1 (Sales), consider routing directly to Sales and making it the timeout default.
  • Abandonment at each level: Where do callers hang up? If most abandonments happen during Level 2 menus, your IVR is too deep.
  • Average time in IVR: How long before callers reach an agent? Under 30 seconds is ideal. Over 60 seconds and you're losing patience-sensitive callers.

For operations that track IVR engagement closely, set up a custom report that joins the call menu logs with the inbound group closer logs to show the complete caller journey from DID to agent connection. This reveals which paths through the IVR produce the highest conversion rates and which paths produce the most abandoned calls.

An IVR that actually works -- clean audio, sensible menus, proper time-based routing, working DTMF -- is the difference between a professional operation and a ringing phone nobody answers. ViciStack builds and tests the entire inbound chain before go-live.

Originally published at https://vicistack.com/blog/vicidial-ivr-setup/

Top comments (0)