Tools: Update: ViciDial IVR Setup with Asterisk — Inbound Call Menus

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

Command

Copy

$ [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