Tools: Update: ViciDial IVR Setup with Asterisk — Inbound Call Menus
ViciDial IVR Setup with Asterisk — Inbound Call Menus
Prerequisites
Understanding ViciDial IVR Architecture
Section 1: Core IVR Dialplan Structure
Creating the Main IVR Context
Connecting DID to IVR Context
Section 2: Database-Driven Menu Logic
Storing Menu Configurations in ViciDial Database
AGI Script for Dynamic Menu Routing
Dialplan Integration with AGI
Section 3: Call Recording and ViciDial Integration
Enabling Call Recording in IVR
Logging IVR Events to ViciDial Database
Section 4: Advanced Features — Caller Authentication and Call Routing
Account Lookup with DTMF Entry
IVR Call Priority Based on Menu Selection
Section 5: Troubleshooting IVR Issues
Common Problems and Solutions
IVR Not Receiving DTMF Input
Calls Dropping After Menu Selection
Recording Files Not Found
IVR Context Not Loading
AGI Script Errors
Queue Timeout Not Working
Database Connection Failures in AGI
Section 6: Monitoring and Testing Your IVR
Asterisk CLI Commands for IVR Debugging
Manual IVR Testing
Accessing ViciDial Logs
Summary Master building production-grade interactive voice response (IVR) systems in ViciDial using Asterisk dialplans, call routing, and real-time menu logic Before starting this tutorial, ensure you have: ViciDial's IVR system sits at the intersection of Asterisk dialplans and the ViciDial database. When an inbound call arrives, it follows this path: The key difference from traditional Asterisk IVR: ViciDial maintains call state in its database, enabling features like call recording, agent integration, and reporting. Your primary IVR context lives in /etc/asterisk/extensions-vicidial.conf. Here's a production-tested template: In your inbound route configuration (via Asterisk or ViciDial UI), point the DID to the from-vicidial-ivr context: Alternatively, configure this in /etc/asterisk/sip.conf: Instead of hardcoding menus in dialplans, use the ViciDial database to define dynamic menus. Create a custom table: Insert sample menu configuration: Create /var/lib/asterisk/agi-bin/vicidial_ivr_router.agi: Make the script executable: Update your main dialplan to use the AGI script: ViciDial requires explicit recording setup. Add this to your IVR entry context: Ensure the recording directory exists and has proper permissions: Create a dialplan macro to log IVR interactions: Allow customers to enter an account number for routing: Route calls with different queue priorities: Problem: Menu plays but pressing buttons doesn't work. Solution: Check for DTMF mode mismatch. ViciDial typically uses RFC2833 (RTP-based): Problem: Call disconnects after pressing a digit. Diagnosis: Check dialplan syntax: Solution: Verify all Goto() statements reference valid contexts and extensions: Problem: Playback fails with "file not found" error. Solution: Ensure audio files exist and are in correct format: Problem: "Unknown context" error when dialing DID. Solution: Reload dialplan and verify syntax: Problem: AGI script returns error, calls fail. Solution: Check script permissions and Perl dependencies: Problem: Calls queue indefinitely instead of timing out. Solution: Verify queue timeout is set in dialplan: Also check queue definition in queues.conf: Problem: AGI script fails to connect to database. Solution: Verify credentials and permissions: Monitor active calls: View queue statistics: Monitor real-time verbose output: Test dialplan routing: Check for syntax errors: From the Asterisk CLI, originate a test call: Test IVR audio playback: View call logs in ViciDial admin: Query call logs directly: Check agent state during IVR interaction: You now have a comprehensive production-grade ViciDial IVR setup with: For production deployment: The IVR system scales with ViciDial's queue management, allowing hundreds of simultaneous calls with intelligent routing based on business rules, not just static menus. Templates let you quickly answer FAQs or store snippets for re-use. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse
$ [from-vicidial-ivr]
; Main IVR entry point for inbound DID calls
exten => s,1,Answer()
exten => s,n,Set(CALLFILENAME=${UNIQUEID})
exten => s,n,Set(CHANNEL(language)=en)
exten => s,n,NoOp(=== ViciDial IVR Entry - CallID: ${UNIQUEID} ===)
exten => s,n,Set(vicidial_log_id=${SHELL(/usr/share/astguiclient/VICIDIAL_log_insert.agi ${CHANNEL(name)} ${EXTEN} ${CALLERID(num)} ${CALLERID(name)})})
exten => s,n,Goto(ivr_main_menu,s,1) [ivr_main_menu]
; Main menu - play prompt and collect input
exten => s,1,NoOp(=== Main Menu ===)
exten => s,n,Set(TIMEOUT(digit)=5)
exten => s,n,Set(TIMEOUT(response)=10)
exten => s,n,Background(/var/spool/asterisk/monolithic/recordings/main_menu_prompt)
exten => s,n,WaitExten(2) ; Menu options - route based on DTMF input
exten => 1,1,Goto(sales_queue,s,1)
exten => 2,1,Goto(support_queue,s,1)
exten => 3,1,Goto(billing_queue,s,1)
exten => 0,1,Goto(operator_queue,s,1) ; Default handler - timeout or invalid input
exten => t,1,Playback(invalid_selection)
exten => t,n,Goto(ivr_main_menu,s,1) exten => i,1,Playback(invalid_selection)
exten => i,n,Goto(ivr_main_menu,s,1) ; Repeat menu option
exten => *,1,Goto(ivr_main_menu,s,1) [sales_queue]
exten => s,1,NoOp(=== Routing to Sales Queue ===)
exten => s,n,Set(CAMPAIGN_ID=sales_inbound)
exten => s,n,Queue(sales_queue,t,,,300)
exten => s,n,Playback(sorry_all_agents_busy)
exten => s,n,VoiceMail(u1000@vicidial)
exten => s,n,Hangup() [support_queue]
exten => s,1,NoOp(=== Routing to Support Queue ===)
exten => s,n,Set(CAMPAIGN_ID=support_inbound)
exten => s,n,Queue(support_queue,t,,,300)
exten => s,n,Playback(sorry_all_agents_busy)
exten => s,n,VoiceMail(u1001@vicidial)
exten => s,n,Hangup() [billing_queue]
exten => s,1,NoOp(=== Routing to Billing Queue ===)
exten => s,n,Set(CAMPAIGN_ID=billing_inbound)
exten => s,n,Queue(billing_queue,t,,,300)
exten => s,n,Playback(sorry_all_agents_busy)
exten => s,n,VoiceMail(u1002@vicidial)
exten => s,n,Hangup() [operator_queue]
exten => s,1,NoOp(=== Routing to Operator Queue ===)
exten => s,n,Set(CAMPAIGN_ID=operator_inbound)
exten => s,n,Queue(operator_queue,t,,,300)
exten => s,n,Hangup()
[from-vicidial-ivr]
; Main IVR entry point for inbound DID calls
exten => s,1,Answer()
exten => s,n,Set(CALLFILENAME=${UNIQUEID})
exten => s,n,Set(CHANNEL(language)=en)
exten => s,n,NoOp(=== ViciDial IVR Entry - CallID: ${UNIQUEID} ===)
exten => s,n,Set(vicidial_log_id=${SHELL(/usr/share/astguiclient/VICIDIAL_log_insert.agi ${CHANNEL(name)} ${EXTEN} ${CALLERID(num)} ${CALLERID(name)})})
exten => s,n,Goto(ivr_main_menu,s,1) [ivr_main_menu]
; Main menu - play prompt and collect input
exten => s,1,NoOp(=== Main Menu ===)
exten => s,n,Set(TIMEOUT(digit)=5)
exten => s,n,Set(TIMEOUT(response)=10)
exten => s,n,Background(/var/spool/asterisk/monolithic/recordings/main_menu_prompt)
exten => s,n,WaitExten(2) ; Menu options - route based on DTMF input
exten => 1,1,Goto(sales_queue,s,1)
exten => 2,1,Goto(support_queue,s,1)
exten => 3,1,Goto(billing_queue,s,1)
exten => 0,1,Goto(operator_queue,s,1) ; Default handler - timeout or invalid input
exten => t,1,Playback(invalid_selection)
exten => t,n,Goto(ivr_main_menu,s,1) exten => i,1,Playback(invalid_selection)
exten => i,n,Goto(ivr_main_menu,s,1) ; Repeat menu option
exten => *,1,Goto(ivr_main_menu,s,1) [sales_queue]
exten => s,1,NoOp(=== Routing to Sales Queue ===)
exten => s,n,Set(CAMPAIGN_ID=sales_inbound)
exten => s,n,Queue(sales_queue,t,,,300)
exten => s,n,Playback(sorry_all_agents_busy)
exten => s,n,VoiceMail(u1000@vicidial)
exten => s,n,Hangup() [support_queue]
exten => s,1,NoOp(=== Routing to Support Queue ===)
exten => s,n,Set(CAMPAIGN_ID=support_inbound)
exten => s,n,Queue(support_queue,t,,,300)
exten => s,n,Playback(sorry_all_agents_busy)
exten => s,n,VoiceMail(u1001@vicidial)
exten => s,n,Hangup() [billing_queue]
exten => s,1,NoOp(=== Routing to Billing Queue ===)
exten => s,n,Set(CAMPAIGN_ID=billing_inbound)
exten => s,n,Queue(billing_queue,t,,,300)
exten => s,n,Playback(sorry_all_agents_busy)
exten => s,n,VoiceMail(u1002@vicidial)
exten => s,n,Hangup() [operator_queue]
exten => s,1,NoOp(=== Routing to Operator Queue ===)
exten => s,n,Set(CAMPAIGN_ID=operator_inbound)
exten => s,n,Queue(operator_queue,t,,,300)
exten => s,n,Hangup()
[from-vicidial-ivr]
; Main IVR entry point for inbound DID calls
exten => s,1,Answer()
exten => s,n,Set(CALLFILENAME=${UNIQUEID})
exten => s,n,Set(CHANNEL(language)=en)
exten => s,n,NoOp(=== ViciDial IVR Entry - CallID: ${UNIQUEID} ===)
exten => s,n,Set(vicidial_log_id=${SHELL(/usr/share/astguiclient/VICIDIAL_log_insert.agi ${CHANNEL(name)} ${EXTEN} ${CALLERID(num)} ${CALLERID(name)})})
exten => s,n,Goto(ivr_main_menu,s,1) [ivr_main_menu]
; Main menu - play prompt and collect input
exten => s,1,NoOp(=== Main Menu ===)
exten => s,n,Set(TIMEOUT(digit)=5)
exten => s,n,Set(TIMEOUT(response)=10)
exten => s,n,Background(/var/spool/asterisk/monolithic/recordings/main_menu_prompt)
exten => s,n,WaitExten(2) ; Menu options - route based on DTMF input
exten => 1,1,Goto(sales_queue,s,1)
exten => 2,1,Goto(support_queue,s,1)
exten => 3,1,Goto(billing_queue,s,1)
exten => 0,1,Goto(operator_queue,s,1) ; Default handler - timeout or invalid input
exten => t,1,Playback(invalid_selection)
exten => t,n,Goto(ivr_main_menu,s,1) exten => i,1,Playback(invalid_selection)
exten => i,n,Goto(ivr_main_menu,s,1) ; Repeat menu option
exten => *,1,Goto(ivr_main_menu,s,1) [sales_queue]
exten => s,1,NoOp(=== Routing to Sales Queue ===)
exten => s,n,Set(CAMPAIGN_ID=sales_inbound)
exten => s,n,Queue(sales_queue,t,,,300)
exten => s,n,Playback(sorry_all_agents_busy)
exten => s,n,VoiceMail(u1000@vicidial)
exten => s,n,Hangup() [support_queue]
exten => s,1,NoOp(=== Routing to Support Queue ===)
exten => s,n,Set(CAMPAIGN_ID=support_inbound)
exten => s,n,Queue(support_queue,t,,,300)
exten => s,n,Playback(sorry_all_agents_busy)
exten => s,n,VoiceMail(u1001@vicidial)
exten => s,n,Hangup() [billing_queue]
exten => s,1,NoOp(=== Routing to Billing Queue ===)
exten => s,n,Set(CAMPAIGN_ID=billing_inbound)
exten => s,n,Queue(billing_queue,t,,,300)
exten => s,n,Playback(sorry_all_agents_busy)
exten => s,n,VoiceMail(u1002@vicidial)
exten => s,n,Hangup() [operator_queue]
exten => s,1,NoOp(=== Routing to Operator Queue ===)
exten => s,n,Set(CAMPAIGN_ID=operator_inbound)
exten => s,n,Queue(operator_queue,t,,,300)
exten => s,n,Hangup()
[from-trunk]
; Receives calls from SIP trunk
exten => 1234567890,1,Goto(from-vicidial-ivr,s,1)
[from-trunk]
; Receives calls from SIP trunk
exten => 1234567890,1,Goto(from-vicidial-ivr,s,1)
[from-trunk]
; Receives calls from SIP trunk
exten => 1234567890,1,Goto(from-vicidial-ivr,s,1)
[trunk-provider]
type=peer
host=your.sip.provider
context=from-trunk
insecure=port,invite
[trunk-provider]
type=peer
host=your.sip.provider
context=from-trunk
insecure=port,invite
[trunk-provider]
type=peer
host=your.sip.provider
context=from-trunk
insecure=port,invite
CREATE TABLE vicidial_ivr_menus ( menu_id INT AUTO_INCREMENT PRIMARY KEY, menu_name VARCHAR(50) UNIQUE NOT NULL, campaign_id VARCHAR(20), prompt_filename VARCHAR(255), timeout_seconds INT DEFAULT 10, max_attempts INT DEFAULT 3, created_date DATETIME DEFAULT CURRENT_TIMESTAMP, modified_date DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, active INT DEFAULT 1
) ENGINE=InnoDB; CREATE TABLE vicidial_ivr_options ( option_id INT AUTO_INCREMENT PRIMARY KEY, menu_id INT NOT NULL, digit_pressed VARCHAR(2), action_type ENUM('goto_menu', 'queue', 'voicemail', 'hangup', 'agi_script'), action_target VARCHAR(100), description VARCHAR(100), sequence INT, FOREIGN KEY (menu_id) REFERENCES vicidial_ivr_menus(menu_id), UNIQUE KEY unique_menu_digit (menu_id, digit_pressed)
) ENGINE=InnoDB;
CREATE TABLE vicidial_ivr_menus ( menu_id INT AUTO_INCREMENT PRIMARY KEY, menu_name VARCHAR(50) UNIQUE NOT NULL, campaign_id VARCHAR(20), prompt_filename VARCHAR(255), timeout_seconds INT DEFAULT 10, max_attempts INT DEFAULT 3, created_date DATETIME DEFAULT CURRENT_TIMESTAMP, modified_date DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, active INT DEFAULT 1
) ENGINE=InnoDB; CREATE TABLE vicidial_ivr_options ( option_id INT AUTO_INCREMENT PRIMARY KEY, menu_id INT NOT NULL, digit_pressed VARCHAR(2), action_type ENUM('goto_menu', 'queue', 'voicemail', 'hangup', 'agi_script'), action_target VARCHAR(100), description VARCHAR(100), sequence INT, FOREIGN KEY (menu_id) REFERENCES vicidial_ivr_menus(menu_id), UNIQUE KEY unique_menu_digit (menu_id, digit_pressed)
) ENGINE=InnoDB;
CREATE TABLE vicidial_ivr_menus ( menu_id INT AUTO_INCREMENT PRIMARY KEY, menu_name VARCHAR(50) UNIQUE NOT NULL, campaign_id VARCHAR(20), prompt_filename VARCHAR(255), timeout_seconds INT DEFAULT 10, max_attempts INT DEFAULT 3, created_date DATETIME DEFAULT CURRENT_TIMESTAMP, modified_date DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, active INT DEFAULT 1
) ENGINE=InnoDB; CREATE TABLE vicidial_ivr_options ( option_id INT AUTO_INCREMENT PRIMARY KEY, menu_id INT NOT NULL, digit_pressed VARCHAR(2), action_type ENUM('goto_menu', 'queue', 'voicemail', 'hangup', 'agi_script'), action_target VARCHAR(100), description VARCHAR(100), sequence INT, FOREIGN KEY (menu_id) REFERENCES vicidial_ivr_menus(menu_id), UNIQUE KEY unique_menu_digit (menu_id, digit_pressed)
) ENGINE=InnoDB;
INSERT INTO vicidial_ivr_menus (menu_name, campaign_id, prompt_filename, timeout_seconds, max_attempts) VALUES
('main_menu', 'ivr_main', '/var/spool/asterisk/monolithic/recordings/main_menu', 10, 3),
('billing_submenu', 'billing_inbound', '/var/spool/asterisk/monolithic/recordings/billing_menu', 8, 2); INSERT INTO vicidial_ivr_options (menu_id, digit_pressed, action_type, action_target, description, sequence) VALUES
(1, '1', 'queue', 'sales_queue', 'Sales Department', 1),
(1, '2', 'queue', 'support_queue', 'Support Department', 2),
(1, '3', 'goto_menu', 'billing_submenu', 'Billing', 3),
(2, '1', 'queue', 'billing_queue', 'Billing Support', 1),
(2, '2', 'voicemail', 'u1002@vicidial', 'Leave Message', 2);
INSERT INTO vicidial_ivr_menus (menu_name, campaign_id, prompt_filename, timeout_seconds, max_attempts) VALUES
('main_menu', 'ivr_main', '/var/spool/asterisk/monolithic/recordings/main_menu', 10, 3),
('billing_submenu', 'billing_inbound', '/var/spool/asterisk/monolithic/recordings/billing_menu', 8, 2); INSERT INTO vicidial_ivr_options (menu_id, digit_pressed, action_type, action_target, description, sequence) VALUES
(1, '1', 'queue', 'sales_queue', 'Sales Department', 1),
(1, '2', 'queue', 'support_queue', 'Support Department', 2),
(1, '3', 'goto_menu', 'billing_submenu', 'Billing', 3),
(2, '1', 'queue', 'billing_queue', 'Billing Support', 1),
(2, '2', 'voicemail', 'u1002@vicidial', 'Leave Message', 2);
INSERT INTO vicidial_ivr_menus (menu_name, campaign_id, prompt_filename, timeout_seconds, max_attempts) VALUES
('main_menu', 'ivr_main', '/var/spool/asterisk/monolithic/recordings/main_menu', 10, 3),
('billing_submenu', 'billing_inbound', '/var/spool/asterisk/monolithic/recordings/billing_menu', 8, 2); INSERT INTO vicidial_ivr_options (menu_id, digit_pressed, action_type, action_target, description, sequence) VALUES
(1, '1', 'queue', 'sales_queue', 'Sales Department', 1),
(1, '2', 'queue', 'support_queue', 'Support Department', 2),
(1, '3', 'goto_menu', 'billing_submenu', 'Billing', 3),
(2, '1', 'queue', 'billing_queue', 'Billing Support', 1),
(2, '2', 'voicemail', 'u1002@vicidial', 'Leave Message', 2);
#!/usr/bin/perl
# ViciDial IVR Router - Dynamic menu handling via database use strict;
use Asterisk::AGI;
use DBI;
use Data::Dumper; my $agi = new Asterisk::AGI; # Database configuration
my $dbhost = 'localhost';
my $dbname = 'asterisk';
my $dbuser = 'vicidial';
my $dbpass = 'your_db_password'; my $dbh = DBI->connect("DBI:mysql:$dbname:$dbhost", $dbuser, $dbpass) or die "Cannot connect: $DBI::errstr\n"; # Get variables from Asterisk
my $menu_name = $agi->get_variable('MENU_NAME');
my $digit_pressed = $agi->get_variable('DIGIT_PRESSED');
my $callid = $agi->get_variable('UNIQUEID'); $agi->verbose("IVR Router: Menu=$menu_name, Digit=$digit_pressed", 3); # Fetch menu configuration
my $menu_query = $dbh->prepare( "SELECT menu_id, prompt_filename, timeout_seconds FROM vicidial_ivr_menus WHERE menu_name = ? AND active = 1"
);
$menu_query->execute($menu_name) or die "Query failed: $DBI::errstr\n";
my $menu = $menu_query->fetchrow_hashref(); unless ($menu) { $agi->exec('Playback', 'invalid_selection'); $agi->set_variable('MENU_ACTION', 'retry'); $dbh->disconnect(); exit(0);
} # Fetch option for pressed digit
my $option_query = $dbh->prepare( "SELECT action_type, action_target FROM vicidial_ivr_options WHERE menu_id = ? AND digit_pressed = ?"
);
$option_query->execute($menu->{menu_id}, $digit_pressed) or die "Query failed: $DBI::errstr\n";
my $option = $option_query->fetchrow_hashref(); if ($option) { $agi->verbose("Executing action: $option->{action_type} -> $option->{action_target}", 3); if ($option->{action_type} eq 'queue') { $agi->set_variable('QUEUE_NAME', $option->{action_target}); $agi->set_variable('MENU_ACTION', 'queue'); } elsif ($option->{action_type} eq 'goto_menu') { $agi->set_variable('MENU_NAME', $option->{action_target}); $agi->set_variable('MENU_ACTION', 'goto_menu'); } elsif ($option->{action_type} eq 'voicemail') { $agi->set_variable('VOICEMAIL_BOX', $option->{action_target}); $agi->set_variable('MENU_ACTION', 'voicemail'); } elsif ($option->{action_type} eq 'hangup') { $agi->set_variable('MENU_ACTION', 'hangup'); }
} else { $agi->verbose("No action defined for digit: $digit_pressed", 2); $agi->set_variable('MENU_ACTION', 'invalid');
} $dbh->disconnect();
exit(0);
#!/usr/bin/perl
# ViciDial IVR Router - Dynamic menu handling via database use strict;
use Asterisk::AGI;
use DBI;
use Data::Dumper; my $agi = new Asterisk::AGI; # Database configuration
my $dbhost = 'localhost';
my $dbname = 'asterisk';
my $dbuser = 'vicidial';
my $dbpass = 'your_db_password'; my $dbh = DBI->connect("DBI:mysql:$dbname:$dbhost", $dbuser, $dbpass) or die "Cannot connect: $DBI::errstr\n"; # Get variables from Asterisk
my $menu_name = $agi->get_variable('MENU_NAME');
my $digit_pressed = $agi->get_variable('DIGIT_PRESSED');
my $callid = $agi->get_variable('UNIQUEID'); $agi->verbose("IVR Router: Menu=$menu_name, Digit=$digit_pressed", 3); # Fetch menu configuration
my $menu_query = $dbh->prepare( "SELECT menu_id, prompt_filename, timeout_seconds FROM vicidial_ivr_menus WHERE menu_name = ? AND active = 1"
);
$menu_query->execute($menu_name) or die "Query failed: $DBI::errstr\n";
my $menu = $menu_query->fetchrow_hashref(); unless ($menu) { $agi->exec('Playback', 'invalid_selection'); $agi->set_variable('MENU_ACTION', 'retry'); $dbh->disconnect(); exit(0);
} # Fetch option for pressed digit
my $option_query = $dbh->prepare( "SELECT action_type, action_target FROM vicidial_ivr_options WHERE menu_id = ? AND digit_pressed = ?"
);
$option_query->execute($menu->{menu_id}, $digit_pressed) or die "Query failed: $DBI::errstr\n";
my $option = $option_query->fetchrow_hashref(); if ($option) { $agi->verbose("Executing action: $option->{action_type} -> $option->{action_target}", 3); if ($option->{action_type} eq 'queue') { $agi->set_variable('QUEUE_NAME', $option->{action_target}); $agi->set_variable('MENU_ACTION', 'queue'); } elsif ($option->{action_type} eq 'goto_menu') { $agi->set_variable('MENU_NAME', $option->{action_target}); $agi->set_variable('MENU_ACTION', 'goto_menu'); } elsif ($option->{action_type} eq 'voicemail') { $agi->set_variable('VOICEMAIL_BOX', $option->{action_target}); $agi->set_variable('MENU_ACTION', 'voicemail'); } elsif ($option->{action_type} eq 'hangup') { $agi->set_variable('MENU_ACTION', 'hangup'); }
} else { $agi->verbose("No action defined for digit: $digit_pressed", 2); $agi->set_variable('MENU_ACTION', 'invalid');
} $dbh->disconnect();
exit(0);
#!/usr/bin/perl
# ViciDial IVR Router - Dynamic menu handling via database use strict;
use Asterisk::AGI;
use DBI;
use Data::Dumper; my $agi = new Asterisk::AGI; # Database configuration
my $dbhost = 'localhost';
my $dbname = 'asterisk';
my $dbuser = 'vicidial';
my $dbpass = 'your_db_password'; my $dbh = DBI->connect("DBI:mysql:$dbname:$dbhost", $dbuser, $dbpass) or die "Cannot connect: $DBI::errstr\n"; # Get variables from Asterisk
my $menu_name = $agi->get_variable('MENU_NAME');
my $digit_pressed = $agi->get_variable('DIGIT_PRESSED');
my $callid = $agi->get_variable('UNIQUEID'); $agi->verbose("IVR Router: Menu=$menu_name, Digit=$digit_pressed", 3); # Fetch menu configuration
my $menu_query = $dbh->prepare( "SELECT menu_id, prompt_filename, timeout_seconds FROM vicidial_ivr_menus WHERE menu_name = ? AND active = 1"
);
$menu_query->execute($menu_name) or die "Query failed: $DBI::errstr\n";
my $menu = $menu_query->fetchrow_hashref(); unless ($menu) { $agi->exec('Playback', 'invalid_selection'); $agi->set_variable('MENU_ACTION', 'retry'); $dbh->disconnect(); exit(0);
} # Fetch option for pressed digit
my $option_query = $dbh->prepare( "SELECT action_type, action_target FROM vicidial_ivr_options WHERE menu_id = ? AND digit_pressed = ?"
);
$option_query->execute($menu->{menu_id}, $digit_pressed) or die "Query failed: $DBI::errstr\n";
my $option = $option_query->fetchrow_hashref(); if ($option) { $agi->verbose("Executing action: $option->{action_type} -> $option->{action_target}", 3); if ($option->{action_type} eq 'queue') { $agi->set_variable('QUEUE_NAME', $option->{action_target}); $agi->set_variable('MENU_ACTION', 'queue'); } elsif ($option->{action_type} eq 'goto_menu') { $agi->set_variable('MENU_NAME', $option->{action_target}); $agi->set_variable('MENU_ACTION', 'goto_menu'); } elsif ($option->{action_type} eq 'voicemail') { $agi->set_variable('VOICEMAIL_BOX', $option->{action_target}); $agi->set_variable('MENU_ACTION', 'voicemail'); } elsif ($option->{action_type} eq 'hangup') { $agi->set_variable('MENU_ACTION', 'hangup'); }
} else { $agi->verbose("No action defined for digit: $digit_pressed", 2); $agi->set_variable('MENU_ACTION', 'invalid');
} $dbh->disconnect();
exit(0);
chmod 755 /var/lib/asterisk/agi-bin/vicidial_ivr_router.agi
chown asterisk:asterisk /var/lib/asterisk/agi-bin/vicidial_ivr_router.agi
chmod 755 /var/lib/asterisk/agi-bin/vicidial_ivr_router.agi
chown asterisk:asterisk /var/lib/asterisk/agi-bin/vicidial_ivr_router.agi
chmod 755 /var/lib/asterisk/agi-bin/vicidial_ivr_router.agi
chown asterisk:asterisk /var/lib/asterisk/agi-bin/vicidial_ivr_router.agi
[ivr_dynamic_menu]
exten => s,1,NoOp(=== Dynamic Menu Handler ===)
exten => s,n,Set(TIMEOUT(digit)=5)
exten => s,n,Set(TIMEOUT(response)=${timeout_seconds})
exten => s,n,Background(${prompt_filename})
exten => s,n,WaitExten(2) exten => 1,1,Set(DIGIT_PRESSED=1)
exten => 1,n,AGI(vicidial_ivr_router.agi)
exten => 1,n,GotoIf($["${MENU_ACTION}"="queue"]?execute_queue,s,1)
exten => 1,n,GotoIf($["${MENU_ACTION}"="goto_menu"]?ivr_dynamic_menu,s,1)
exten => 1,n,GotoIf($["${MENU_ACTION}"="voicemail"]?execute_voicemail,s,1)
exten => 1,n,Hangup() exten => 2,1,Set(DIGIT_PRESSED=2)
exten => 2,n,AGI(vicidial_ivr_router.agi)
exten => 2,n,GotoIf($["${MENU_ACTION}"="queue"]?execute_queue,s,1)
exten => 2,n,GotoIf($["${MENU_ACTION}"="goto_menu"]?ivr_dynamic_menu,s,1)
exten => 2,n,Hangup() ; Repeat for digits 3-9... exten => t,1,Playback(invalid_selection)
exten => t,n,Goto(ivr_dynamic_menu,s,1) exten => i,1,Playback(invalid_selection)
exten => i,n,Goto(ivr_dynamic_menu,s,1) [execute_queue]
exten => s,1,Queue(${QUEUE_NAME},t,,,300)
exten => s,n,Playback(all_agents_busy)
exten => s,n,Hangup() [execute_voicemail]
exten => s,1,VoiceMail(${VOICEMAIL_BOX})
exten => s,n,Hangup()
[ivr_dynamic_menu]
exten => s,1,NoOp(=== Dynamic Menu Handler ===)
exten => s,n,Set(TIMEOUT(digit)=5)
exten => s,n,Set(TIMEOUT(response)=${timeout_seconds})
exten => s,n,Background(${prompt_filename})
exten => s,n,WaitExten(2) exten => 1,1,Set(DIGIT_PRESSED=1)
exten => 1,n,AGI(vicidial_ivr_router.agi)
exten => 1,n,GotoIf($["${MENU_ACTION}"="queue"]?execute_queue,s,1)
exten => 1,n,GotoIf($["${MENU_ACTION}"="goto_menu"]?ivr_dynamic_menu,s,1)
exten => 1,n,GotoIf($["${MENU_ACTION}"="voicemail"]?execute_voicemail,s,1)
exten => 1,n,Hangup() exten => 2,1,Set(DIGIT_PRESSED=2)
exten => 2,n,AGI(vicidial_ivr_router.agi)
exten => 2,n,GotoIf($["${MENU_ACTION}"="queue"]?execute_queue,s,1)
exten => 2,n,GotoIf($["${MENU_ACTION}"="goto_menu"]?ivr_dynamic_menu,s,1)
exten => 2,n,Hangup() ; Repeat for digits 3-9... exten => t,1,Playback(invalid_selection)
exten => t,n,Goto(ivr_dynamic_menu,s,1) exten => i,1,Playback(invalid_selection)
exten => i,n,Goto(ivr_dynamic_menu,s,1) [execute_queue]
exten => s,1,Queue(${QUEUE_NAME},t,,,300)
exten => s,n,Playback(all_agents_busy)
exten => s,n,Hangup() [execute_voicemail]
exten => s,1,VoiceMail(${VOICEMAIL_BOX})
exten => s,n,Hangup()
[ivr_dynamic_menu]
exten => s,1,NoOp(=== Dynamic Menu Handler ===)
exten => s,n,Set(TIMEOUT(digit)=5)
exten => s,n,Set(TIMEOUT(response)=${timeout_seconds})
exten => s,n,Background(${prompt_filename})
exten => s,n,WaitExten(2) exten => 1,1,Set(DIGIT_PRESSED=1)
exten => 1,n,AGI(vicidial_ivr_router.agi)
exten => 1,n,GotoIf($["${MENU_ACTION}"="queue"]?execute_queue,s,1)
exten => 1,n,GotoIf($["${MENU_ACTION}"="goto_menu"]?ivr_dynamic_menu,s,1)
exten => 1,n,GotoIf($["${MENU_ACTION}"="voicemail"]?execute_voicemail,s,1)
exten => 1,n,Hangup() exten => 2,1,Set(DIGIT_PRESSED=2)
exten => 2,n,AGI(vicidial_ivr_router.agi)
exten => 2,n,GotoIf($["${MENU_ACTION}"="queue"]?execute_queue,s,1)
exten => 2,n,GotoIf($["${MENU_ACTION}"="goto_menu"]?ivr_dynamic_menu,s,1)
exten => 2,n,Hangup() ; Repeat for digits 3-9... exten => t,1,Playback(invalid_selection)
exten => t,n,Goto(ivr_dynamic_menu,s,1) exten => i,1,Playback(invalid_selection)
exten => i,n,Goto(ivr_dynamic_menu,s,1) [execute_queue]
exten => s,1,Queue(${QUEUE_NAME},t,,,300)
exten => s,n,Playback(all_agents_busy)
exten => s,n,Hangup() [execute_voicemail]
exten => s,1,VoiceMail(${VOICEMAIL_BOX})
exten => s,n,Hangup()
[from-vicidial-ivr]
exten => s,1,Answer()
exten => s,n,Set(CALLFILENAME=${STRFTIME(${EPOCH},,%Y%m%d-%H%M%S)}-${UNIQUEID})
exten => s,n,Set(CHANNEL(language)=en)
exten => s,n,MixMonitor(/var/spool/asterisk/monolithic/recordings/ivr/${CALLFILENAME}.wav,b)
exten => s,n,Set(RECORDING_FILE=/var/spool/asterisk/monolithic/recordings/ivr/${CALLFILENAME}.wav)
exten => s,n,NoOp(=== ViciDial IVR Recording: ${RECORDING_FILE} ===)
exten => s,n,Goto(ivr_main_menu,s,1)
[from-vicidial-ivr]
exten => s,1,Answer()
exten => s,n,Set(CALLFILENAME=${STRFTIME(${EPOCH},,%Y%m%d-%H%M%S)}-${UNIQUEID})
exten => s,n,Set(CHANNEL(language)=en)
exten => s,n,MixMonitor(/var/spool/asterisk/monolithic/recordings/ivr/${CALLFILENAME}.wav,b)
exten => s,n,Set(RECORDING_FILE=/var/spool/asterisk/monolithic/recordings/ivr/${CALLFILENAME}.wav)
exten => s,n,NoOp(=== ViciDial IVR Recording: ${RECORDING_FILE} ===)
exten => s,n,Goto(ivr_main_menu,s,1)
[from-vicidial-ivr]
exten => s,1,Answer()
exten => s,n,Set(CALLFILENAME=${STRFTIME(${EPOCH},,%Y%m%d-%H%M%S)}-${UNIQUEID})
exten => s,n,Set(CHANNEL(language)=en)
exten => s,n,MixMonitor(/var/spool/asterisk/monolithic/recordings/ivr/${CALLFILENAME}.wav,b)
exten => s,n,Set(RECORDING_FILE=/var/spool/asterisk/monolithic/recordings/ivr/${CALLFILENAME}.wav)
exten => s,n,NoOp(=== ViciDial IVR Recording: ${RECORDING_FILE} ===)
exten => s,n,Goto(ivr_main_menu,s,1)
mkdir -p /var/spool/asterisk/monolithic/recordings/ivr
chown asterisk:asterisk /var/spool/asterisk/monolithic/recordings/ivr
chmod 755 /var/spool/asterisk/monolithic/recordings/ivr
mkdir -p /var/spool/asterisk/monolithic/recordings/ivr
chown asterisk:asterisk /var/spool/asterisk/monolithic/recordings/ivr
chmod 755 /var/spool/asterisk/monolithic/recordings/ivr
mkdir -p /var/spool/asterisk/monolithic/recordings/ivr
chown asterisk:asterisk /var/spool/asterisk/monolithic/recordings/ivr
chmod 755 /var/spool/asterisk/monolithic/recordings/ivr
[macro-ivr_log_event]
; Arguments: ${ARG1}=event_type, ${ARG2}=menu_name, ${ARG3}=digit_pressed
exten => s,1,NoOp(=== Logging IVR Event ===)
exten => s,n,AGI(agi://<ivr_logger_script>,${ARG1},${ARG2},${ARG3}) [from-vicidial-ivr-logged]
exten => s,1,Answer()
exten => s,n,Set(CALLFILENAME=${UNIQUEID})
exten => s,n,MixMonitor(/var/spool/asterisk/monolithic/recordings/ivr/${CALLFILENAME}.wav,b)
exten => s,n,Macro(ivr_log_event,ivr_entry,main_menu,)
exten => s,n,Goto(ivr_main_menu,s,1) [ivr_main_menu]
exten => s,1,Set(TIMEOUT(digit)=5)
exten => s,n,Set(TIMEOUT(response)=10)
exten => s,n,Background(/var/spool/asterisk/monolithic/recordings/main_menu_prompt)
exten => s,n,WaitExten(2) exten => 1,1,Macro(ivr_log_event,digit_pressed,main_menu,1)
exten => 1,n,Goto(sales_queue,s,1) exten => 2,1,Macro(ivr_log_event,digit_pressed,main_menu,2)
exten => 2,n,Goto(support_queue,s,1) exten => t,1,Macro(ivr_log_event,timeout,main_menu,)
exten => t,n,Goto(ivr_main_menu,s,1) exten => i,1,Macro(ivr_log_event,invalid_input,main_menu,)
exten => i,n,Goto(ivr_main_menu,s,1)
[macro-ivr_log_event]
; Arguments: ${ARG1}=event_type, ${ARG2}=menu_name, ${ARG3}=digit_pressed
exten => s,1,NoOp(=== Logging IVR Event ===)
exten => s,n,AGI(agi://<ivr_logger_script>,${ARG1},${ARG2},${ARG3}) [from-vicidial-ivr-logged]
exten => s,1,Answer()
exten => s,n,Set(CALLFILENAME=${UNIQUEID})
exten => s,n,MixMonitor(/var/spool/asterisk/monolithic/recordings/ivr/${CALLFILENAME}.wav,b)
exten => s,n,Macro(ivr_log_event,ivr_entry,main_menu,)
exten => s,n,Goto(ivr_main_menu,s,1) [ivr_main_menu]
exten => s,1,Set(TIMEOUT(digit)=5)
exten => s,n,Set(TIMEOUT(response)=10)
exten => s,n,Background(/var/spool/asterisk/monolithic/recordings/main_menu_prompt)
exten => s,n,WaitExten(2) exten => 1,1,Macro(ivr_log_event,digit_pressed,main_menu,1)
exten => 1,n,Goto(sales_queue,s,1) exten => 2,1,Macro(ivr_log_event,digit_pressed,main_menu,2)
exten => 2,n,Goto(support_queue,s,1) exten => t,1,Macro(ivr_log_event,timeout,main_menu,)
exten => t,n,Goto(ivr_main_menu,s,1) exten => i,1,Macro(ivr_log_event,invalid_input,main_menu,)
exten => i,n,Goto(ivr_main_menu,s,1)
[macro-ivr_log_event]
; Arguments: ${ARG1}=event_type, ${ARG2}=menu_name, ${ARG3}=digit_pressed
exten => s,1,NoOp(=== Logging IVR Event ===)
exten => s,n,AGI(agi://<ivr_logger_script>,${ARG1},${ARG2},${ARG3}) [from-vicidial-ivr-logged]
exten => s,1,Answer()
exten => s,n,Set(CALLFILENAME=${UNIQUEID})
exten => s,n,MixMonitor(/var/spool/asterisk/monolithic/recordings/ivr/${CALLFILENAME}.wav,b)
exten => s,n,Macro(ivr_log_event,ivr_entry,main_menu,)
exten => s,n,Goto(ivr_main_menu,s,1) [ivr_main_menu]
exten => s,1,Set(TIMEOUT(digit)=5)
exten => s,n,Set(TIMEOUT(response)=10)
exten => s,n,Background(/var/spool/asterisk/monolithic/recordings/main_menu_prompt)
exten => s,n,WaitExten(2) exten => 1,1,Macro(ivr_log_event,digit_pressed,main_menu,1)
exten => 1,n,Goto(sales_queue,s,1) exten => 2,1,Macro(ivr_log_event,digit_pressed,main_menu,2)
exten => 2,n,Goto(support_queue,s,1) exten => t,1,Macro(ivr_log_event,timeout,main_menu,)
exten => t,n,Goto(ivr_main_menu,s,1) exten => i,1,Macro(ivr_log_event,invalid_input,main_menu,)
exten => i,n,Goto(ivr_main_menu,s,1)
[ivr_account_lookup]
exten => s,1,NoOp(=== Account Lookup ===)
exten => s,n,Set(TIMEOUT(digit)=5)
exten => s,n,Playback(enter_account_number)
exten => s,n,Read(ACCOUNT_NUMBER,please_enter_account)
exten => s,n,AGI(agi://<account_validation>,${ACCOUNT_NUMBER})
exten => s,n,GotoIf($["${ACCOUNT_VALID}"="1"]?lookup_success,s,1)
exten => s,n,Playback(account_not_found)
exten => s,n,Goto(ivr_main_menu,s,1) [lookup_success]
exten => s,1,NoOp(=== Account Found: ${ACCOUNT_NUMBER} ===)
exten => s,n,Set(CAMPAIGN_ID=${ACCOUNT_CAMPAIGN})
exten => s,n,Queue(${ACCOUNT_QUEUE},t,,,300)
exten => s,n,Playback(sorry_all_agents_busy)
exten => s,n,Hangup()
[ivr_account_lookup]
exten => s,1,NoOp(=== Account Lookup ===)
exten => s,n,Set(TIMEOUT(digit)=5)
exten => s,n,Playback(enter_account_number)
exten => s,n,Read(ACCOUNT_NUMBER,please_enter_account)
exten => s,n,AGI(agi://<account_validation>,${ACCOUNT_NUMBER})
exten => s,n,GotoIf($["${ACCOUNT_VALID}"="1"]?lookup_success,s,1)
exten => s,n,Playback(account_not_found)
exten => s,n,Goto(ivr_main_menu,s,1) [lookup_success]
exten => s,1,NoOp(=== Account Found: ${ACCOUNT_NUMBER} ===)
exten => s,n,Set(CAMPAIGN_ID=${ACCOUNT_CAMPAIGN})
exten => s,n,Queue(${ACCOUNT_QUEUE},t,,,300)
exten => s,n,Playback(sorry_all_agents_busy)
exten => s,n,Hangup()
[ivr_account_lookup]
exten => s,1,NoOp(=== Account Lookup ===)
exten => s,n,Set(TIMEOUT(digit)=5)
exten => s,n,Playback(enter_account_number)
exten => s,n,Read(ACCOUNT_NUMBER,please_enter_account)
exten => s,n,AGI(agi://<account_validation>,${ACCOUNT_NUMBER})
exten => s,n,GotoIf($["${ACCOUNT_VALID}"="1"]?lookup_success,s,1)
exten => s,n,Playback(account_not_found)
exten => s,n,Goto(ivr_main_menu,s,1) [lookup_success]
exten => s,1,NoOp(=== Account Found: ${ACCOUNT_NUMBER} ===)
exten => s,n,Set(CAMPAIGN_ID=${ACCOUNT_CAMPAIGN})
exten => s,n,Queue(${ACCOUNT_QUEUE},t,,,300)
exten => s,n,Playback(sorry_all_agents_busy)
exten => s,n,Hangup()
[ivr_priority_menu]
exten => s,1,NoOp(=== Priority Menu ===)
exten => s,n,Set(TIMEOUT(digit)=5)
exten => s,n,Set(TIMEOUT(response)=10)
exten => s,n,Background(/var/spool/asterisk/monolithic/recordings/priority_menu)
exten => s,n,WaitExten(2) ; High priority (existing customers)
exten => 1,1,Set(QUEUE_PRIORITY=10)
exten => 1,n,Set(CALL_TYPE=existing_customer)
exten => 1,n,Goto(route_to_queue,s,1) ; Standard priority (new customers)
exten => 2,1,Set(QUEUE_PRIORITY=5)
exten => 2,n,Set(CALL_TYPE=new_customer)
exten => 2,n,Goto(route_to_queue,s,1) ; VIP priority
exten => 3,1,Set(QUEUE_PRIORITY=15)
exten => 3,n,Set(CALL_TYPE=vip_customer)
exten => 3,n,Goto(route_to_queue,s,1) [route_to_queue]
exten => s,1,NoOp(=== Queue with Priority ${QUEUE_PRIORITY} ===)
exten => s,n,Set(QUEUE_PRIORITY=${QUEUE_PRIORITY})
exten => s,n,Queue(sales_queue,t,,,300)
exten => s,n,Hangup()
[ivr_priority_menu]
exten => s,1,NoOp(=== Priority Menu ===)
exten => s,n,Set(TIMEOUT(digit)=5)
exten => s,n,Set(TIMEOUT(response)=10)
exten => s,n,Background(/var/spool/asterisk/monolithic/recordings/priority_menu)
exten => s,n,WaitExten(2) ; High priority (existing customers)
exten => 1,1,Set(QUEUE_PRIORITY=10)
exten => 1,n,Set(CALL_TYPE=existing_customer)
exten => 1,n,Goto(route_to_queue,s,1) ; Standard priority (new customers)
exten => 2,1,Set(QUEUE_PRIORITY=5)
exten => 2,n,Set(CALL_TYPE=new_customer)
exten => 2,n,Goto(route_to_queue,s,1) ; VIP priority
exten => 3,1,Set(QUEUE_PRIORITY=15)
exten => 3,n,Set(CALL_TYPE=vip_customer)
exten => 3,n,Goto(route_to_queue,s,1) [route_to_queue]
exten => s,1,NoOp(=== Queue with Priority ${QUEUE_PRIORITY} ===)
exten => s,n,Set(QUEUE_PRIORITY=${QUEUE_PRIORITY})
exten => s,n,Queue(sales_queue,t,,,300)
exten => s,n,Hangup()
[ivr_priority_menu]
exten => s,1,NoOp(=== Priority Menu ===)
exten => s,n,Set(TIMEOUT(digit)=5)
exten => s,n,Set(TIMEOUT(response)=10)
exten => s,n,Background(/var/spool/asterisk/monolithic/recordings/priority_menu)
exten => s,n,WaitExten(2) ; High priority (existing customers)
exten => 1,1,Set(QUEUE_PRIORITY=10)
exten => 1,n,Set(CALL_TYPE=existing_customer)
exten => 1,n,Goto(route_to_queue,s,1) ; Standard priority (new customers)
exten => 2,1,Set(QUEUE_PRIORITY=5)
exten => 2,n,Set(CALL_TYPE=new_customer)
exten => 2,n,Goto(route_to_queue,s,1) ; VIP priority
exten => 3,1,Set(QUEUE_PRIORITY=15)
exten => 3,n,Set(CALL_TYPE=vip_customer)
exten => 3,n,Goto(route_to_queue,s,1) [route_to_queue]
exten => s,1,NoOp(=== Queue with Priority ${QUEUE_PRIORITY} ===)
exten => s,n,Set(QUEUE_PRIORITY=${QUEUE_PRIORITY})
exten => s,n,Queue(sales_queue,t,,,300)
exten => s,n,Hangup()
asterisk -rx "sip set debug on"
asterisk -rx "core set verbose 3"
tail -f /var/log/asterisk/messages | grep -i dtmf
asterisk -rx "sip set debug on"
asterisk -rx "core set verbose 3"
tail -f /var/log/asterisk/messages | grep -i dtmf
asterisk -rx "sip set debug on"
asterisk -rx "core set verbose 3"
tail -f /var/log/asterisk/messages | grep -i dtmf
[sip-vicidial]
dtmfmode=rfc2833
[sip-vicidial]
dtmfmode=rfc2833
[sip-vicidial]
dtmfmode=rfc2833
-weight: 500;">systemctl -weight: 500;">restart asterisk
-weight: 500;">systemctl -weight: 500;">restart asterisk
-weight: 500;">systemctl -weight: 500;">restart asterisk
asterisk -rx "dialplan reload"
asterisk -rx "dialplan show from-vicidial-ivr" | grep -A5 "exten => 1"
asterisk -rx "dialplan reload"
asterisk -rx "dialplan show from-vicidial-ivr" | grep -A5 "exten => 1"
asterisk -rx "dialplan reload"
asterisk -rx "dialplan show from-vicidial-ivr" | grep -A5 "exten => 1"
exten => 1,1,Goto(sales_queue,s,1)
; Verify that 'sales_queue' context exists with 's' extension
exten => 1,1,Goto(sales_queue,s,1)
; Verify that 'sales_queue' context exists with 's' extension
exten => 1,1,Goto(sales_queue,s,1)
; Verify that 'sales_queue' context exists with 's' extension
ls -la /var/spool/asterisk/monolithic/recordings/
asterisk -rx "core set verbose 3" | grep -i playback
ls -la /var/spool/asterisk/monolithic/recordings/
asterisk -rx "core set verbose 3" | grep -i playback
ls -la /var/spool/asterisk/monolithic/recordings/
asterisk -rx "core set verbose 3" | grep -i playback
# Check format
file /var/spool/asterisk/monolithic/recordings/main_menu_prompt.wav # Convert if necessary
sox input.wav -b 8 -r 8000 output.wav remix - # Test playback
asterisk -rx "core set verbose 3"
# In dialplan: Playback(/var/spool/asterisk/monolithic/recordings/test_prompt)
# Check format
file /var/spool/asterisk/monolithic/recordings/main_menu_prompt.wav # Convert if necessary
sox input.wav -b 8 -r 8000 output.wav remix - # Test playback
asterisk -rx "core set verbose 3"
# In dialplan: Playback(/var/spool/asterisk/monolithic/recordings/test_prompt)
# Check format
file /var/spool/asterisk/monolithic/recordings/main_menu_prompt.wav # Convert if necessary
sox input.wav -b 8 -r 8000 output.wav remix - # Test playback
asterisk -rx "core set verbose 3"
# In dialplan: Playback(/var/spool/asterisk/monolithic/recordings/test_prompt)
asterisk -rx "dialplan show from-vicidial-ivr"
asterisk -rx "core show functions" | grep Load
asterisk -rx "dialplan show from-vicidial-ivr"
asterisk -rx "core show functions" | grep Load
asterisk -rx "dialplan show from-vicidial-ivr"
asterisk -rx "core show functions" | grep Load
# Check syntax first
asterisk -vvvvvc -g 2>&1 | head -50 # Reload
asterisk -rx "dialplan reload" # Verify
asterisk -rx "dialplan show from-vicidial-ivr" | head -20
# Check syntax first
asterisk -vvvvvc -g 2>&1 | head -50 # Reload
asterisk -rx "dialplan reload" # Verify
asterisk -rx "dialplan show from-vicidial-ivr" | head -20
# Check syntax first
asterisk -vvvvvc -g 2>&1 | head -50 # Reload
asterisk -rx "dialplan reload" # Verify
asterisk -rx "dialplan show from-vicidial-ivr" | head -20
tail -f /var/log/asterisk/messages | grep agi
asterisk -rx "agi debug on"
tail -f /var/log/asterisk/messages | grep agi
asterisk -rx "agi debug on"
tail -f /var/log/asterisk/messages | grep agi
asterisk -rx "agi debug on"
chmod 755 /var/lib/asterisk/agi-bin/vicidial_ivr_router.agi
perl -c /var/lib/asterisk/agi-bin/vicidial_ivr_router.agi
cpan -weight: 500;">install Asterisk::AGI
chmod 755 /var/lib/asterisk/agi-bin/vicidial_ivr_router.agi
perl -c /var/lib/asterisk/agi-bin/vicidial_ivr_router.agi
cpan -weight: 500;">install Asterisk::AGI
chmod 755 /var/lib/asterisk/agi-bin/vicidial_ivr_router.agi
perl -c /var/lib/asterisk/agi-bin/vicidial_ivr_router.agi
cpan -weight: 500;">install Asterisk::AGI
asterisk -rx "queue show sales_queue"
asterisk -rx "queue show sales_queue"
asterisk -rx "queue show sales_queue"
exten => s,1,Queue(sales_queue,t,,,300)
; ^^^ timeout in seconds
exten => s,1,Queue(sales_queue,t,,,300)
; ^^^ timeout in seconds
exten => s,1,Queue(sales_queue,t,,,300)
; ^^^ timeout in seconds
[sales_queue]
member => SIP/agent1
member => SIP/agent2
timeout=20
[sales_queue]
member => SIP/agent1
member => SIP/agent2
timeout=20
[sales_queue]
member => SIP/agent1
member => SIP/agent2
timeout=20
mysql -h localhost -u vicidial -p asterisk -e "SELECT COUNT(*) FROM vicidial_ivr_menus;"
mysql -h localhost -u vicidial -p asterisk -e "SELECT COUNT(*) FROM vicidial_ivr_menus;"
mysql -h localhost -u vicidial -p asterisk -e "SELECT COUNT(*) FROM vicidial_ivr_menus;"
# From MySQL:
GRANT SELECT ON asterisk.vicidial_ivr_menus TO 'vicidial'@'localhost';
FLUSH PRIVILEGES; # Test Perl DBI connection
perl -e 'use DBI; my $dbh = DBI->connect("DBI:mysql:asterisk:localhost", "vicidial", "password"); print "OK\n";'
# From MySQL:
GRANT SELECT ON asterisk.vicidial_ivr_menus TO 'vicidial'@'localhost';
FLUSH PRIVILEGES; # Test Perl DBI connection
perl -e 'use DBI; my $dbh = DBI->connect("DBI:mysql:asterisk:localhost", "vicidial", "password"); print "OK\n";'
# From MySQL:
GRANT SELECT ON asterisk.vicidial_ivr_menus TO 'vicidial'@'localhost';
FLUSH PRIVILEGES; # Test Perl DBI connection
perl -e 'use DBI; my $dbh = DBI->connect("DBI:mysql:asterisk:localhost", "vicidial", "password"); print "OK\n";'
asterisk -rx "core show calls"
asterisk -rx "core show calls"
asterisk -rx "core show calls"
asterisk -rx "queue show sales_queue"
asterisk -rx "queue show sales_queue"
asterisk -rx "queue show sales_queue"
asterisk -rx "core set verbose 3"
tail -f /var/log/asterisk/messages
asterisk -rx "core set verbose 3"
tail -f /var/log/asterisk/messages
asterisk -rx "core set verbose 3"
tail -f /var/log/asterisk/messages
asterisk -rx "dialplan eval 1234567890 from-vicidial-ivr s"
asterisk -rx "dialplan eval 1234567890 from-vicidial-ivr s"
asterisk -rx "dialplan eval 1234567890 from-vicidial-ivr s"
asterisk -rx "dialplan reload"
asterisk -rx "dialplan reload"
asterisk -rx "dialplan reload"
asterisk -rx "channel originate SIP/test-agent Extension 1000@from-vicidial-ivr"
asterisk -rx "channel originate SIP/test-agent Extension 1000@from-vicidial-ivr"
asterisk -rx "channel originate SIP/test-agent Extension 1000@from-vicidial-ivr"
asterisk -rx "console dial 1000@from-vicidial-ivr"
asterisk -rx "console dial 1000@from-vicidial-ivr"
asterisk -rx "console dial 1000@from-vicidial-ivr"
SELECT * FROM vicidial_log WHERE called_number = '1234567890' AND call_date >= NOW() - INTERVAL 1 HOUR ORDER BY call_date DESC LIMIT 20;
SELECT * FROM vicidial_log WHERE called_number = '1234567890' AND call_date >= NOW() - INTERVAL 1 HOUR ORDER BY call_date DESC LIMIT 20;
SELECT * FROM vicidial_log WHERE called_number = '1234567890' AND call_date >= NOW() - INTERVAL 1 HOUR ORDER BY call_date DESC LIMIT 20;
SELECT user, -weight: 500;">status, phone_number, call_date FROM vicidial_agent_log WHERE call_date >= NOW() - INTERVAL 1 HOUR ORDER BY call_date DESC;
SELECT user, -weight: 500;">status, phone_number, call_date FROM vicidial_agent_log WHERE call_date >= NOW() - INTERVAL 1 HOUR ORDER BY call_date DESC;
SELECT user, -weight: 500;">status, phone_number, call_date FROM vicidial_agent_log WHERE call_date >= NOW() - INTERVAL 1 HOUR ORDER BY call_date DESC; - ViciDial 2.14+ installed and running (tested on CentOS 7/8 or Ubuntu 18.04+)
- Asterisk 13+ compiled with res_agi, res_http_post, and res_voicemail enabled
- Root or -weight: 600;">sudo access to the ViciDial server
- A working inbound DID or test extension
- MySQL/MariaDB database access with vicidial user permissions
- Basic understanding of Asterisk dialplans and ViciDial agent flow
- Recorded audio prompts in WAV format (8-bit, 8000 Hz mono recommended)
- At least one active campaign in ViciDial with inbound call handling enabled - DID routes to an Asterisk context (usually defined in extensions-vicidial.conf)
- IVR dialplan plays prompts and collects DTMF input
- Digits trigger logic that queries ViciDial database or executes AGI scripts
- Calls route to campaigns, queues, or agents based on menu selections
- Call metadata logs to vicidial_log table - Navigate to /vicidial/admin.php
- Reports → Call Log
- Filter by DID or time range - Core IVR dialplans that route inbound calls through menu hierarchies
- Database-driven dynamic menus using custom tables for flexible configuration
- AGI script integration for intelligent call routing and account lookup
- Call recording and logging that integrates with ViciDial's reporting
- Advanced features including caller authentication, priority routing, and multi-step menus
- Troubleshooting techniques for common IVR issues
- Monitoring tools to track and debug call flows in real-time - Backup your Asterisk configuration before making changes
- Test all dialplans with test calls before going live
- Monitor logs during the first 24 hours after deployment
- Set appropriate queue timeouts based on your staffing
- Record audio prompts professionally to improve caller experience
- Document custom contexts for future maintenance
- Implement call recording for compliance and training
- Schedule regular database maintenance on your custom IVR tables