Master ViciDial's timezone routing capabilities to deliver calls to the right prospect at the right local time, increasing answer rates and compliance with do-not-call regulations.
Introduction
Time zone routing in ViciDial is one of the most underutilized yet powerful features for improving contact center efficiency. Whether you're running a multi-state sales operation, managing customer support across regions, or navigating strict calling hour restrictions, intelligent timezone-aware call delivery directly impacts your metrics.
This tutorial covers the practical implementation of time zone routing in ViciDial/Asterisk production environments—from database configuration and campaign setup through real-time call routing logic and validation.
Prerequisites
Before implementing timezone routing, ensure you have:
- ViciDial 2.13+ installed and fully functional (2.14.1+ recommended for latest timezone fixes)
- Asterisk 13+ with proper TLS/SIP configuration
- MariaDB/MySQL root or administrative access
- SSH access to your ViciDial server with sudo privileges
- Existing campaigns configured with at least one carrier/trunk
- Basic understanding of ViciDial admin interface, MySQL, and Asterisk dialplan
- Timezone database files at
/usr/share/zoneinfo/(standard on Linux) - Test phone numbers across multiple time zones for validation
Understanding ViciDial's Time Zone Architecture
How ViciDial Handles Time Zones
ViciDial stores timezone information at multiple levels:
- Server timezone — System timezone on the ViciDial machine
- Campaign timezone — Default timezone for a campaign
- Lead timezone — Individual prospect timezone (overrides campaign setting)
- Carrier timezone — Routing rules per carrier
- Agent timezone — Desktop agent location (for callback scenarios)
The ViciDial database uses the vicidial_campaigns table to store campaign-level timezone settings, and vicidial_list table stores lead-specific timezone data. When a call is queued, the dialer checks these values against the current time to determine if a call should be placed or held.
Core Database Tables
Three tables handle timezone logic:
vicidial_campaigns.campaign_timezone (varchar 45)
vicidial_campaigns.lead_filter_level (int, affects timezone logic)
vicidial_list.lead_timezone (varchar 45)
The dialer processes leads in order, but only initiates calls when the prospect's local time falls within allowed calling hours.
Configuring Campaign-Level Time Zone Settings
Step 1: Set Default Campaign Timezone
Access your ViciDial admin interface and navigate to Campaigns → Edit Campaign. Or directly update the database:
UPDATE vicidial_campaigns
SET campaign_timezone = 'America/New_York'
WHERE campaign_id = 'TEST_CAMP';
Valid timezone strings use the IANA timezone database format. Common examples:
America/New_York
America/Chicago
America/Denver
America/Los_Angeles
America/Anchorage
Pacific/Honolulu
Europe/London
Europe/Paris
Asia/Tokyo
Australia/Sydney
Step 2: Configure Calling Hours
Calling hours are stored in the vicidial_campaigns table in two fields:
start_call_hour(integer 0-23)end_call_hour(integer 0-23)
Set these to your business hours in the campaign timezone:
UPDATE vicidial_campaigns
SET start_call_hour = 9,
end_call_hour = 20
WHERE campaign_id = 'TEST_CAMP';
This restricts all calls to 9:00 AM – 8:00 PM prospect local time.
Step 3: Enable Hour-Based Lead Filtering
The lead_filter_level controls how aggressively the dialer filters calls by time:
UPDATE vicidial_campaigns
SET lead_filter_level = 1
WHERE campaign_id = 'TEST_CAMP';
Values:
0— No time-based filtering (call anytime)1— Filter by start/end hours only2— Filter by hours + respect timezone (recommended)3— Strict mode (filter even if uncertain)
For production multi-timezone campaigns, use level 2.
Lead-Level Timezone Assignment
Bulk Assign Timezones
If your leads import without timezone data, assign them based on area codes or geographic data:
-- Assign New York timezone to 212 area code
UPDATE vicidial_list
SET lead_timezone = 'America/New_York'
WHERE phone_number LIKE '212%'
AND campaign_id = 'TEST_CAMP';
-- Assign Pacific timezone to 415 area code
UPDATE vicidial_list
SET lead_timezone = 'America/Los_Angeles'
WHERE phone_number LIKE '415%'
AND campaign_id = 'TEST_CAMP';
Import Timezones from CSV
When importing leads via ViciDial's upload interface, include a lead_timezone column:
phone_number,first_name,last_name,lead_timezone
5551234567,John,Doe,America/New_York
7075551234,Jane,Smith,America/Los_Angeles
3125559876,Bob,Johnson,America/Chicago
The ViciDial upload processor automatically populates the lead_timezone field.
Verify Timezone Data
Check that timezones are properly assigned:
SELECT lead_id, phone_number, lead_timezone,
DATE_FORMAT(NOW(), '%H:%i:%s') AS 'Server_Time'
FROM vicidial_list
WHERE campaign_id = 'TEST_CAMP'
LIMIT 10;
Sample output:
| lead_id | phone_number | lead_timezone | Server_Time |
|---------|--------------|---------------------|-------------|
| 1 | 5551234567 | America/New_York | 14:30:45 |
| 2 | 7075556789 | America/Los_Angeles | 14:30:45 |
Advanced Carrier and Trunk Routing
Route Calls by Time Zone and Carrier
For operations with geographically distributed carriers, route calls through region-specific trunks to minimize latency and comply with local regulations:
Edit your Asterisk extensions file at /etc/asterisk/extensions-vicidial.conf:
[from-vicidial-dialer]
exten => _X.,1,NoOp(ViciDial Call Router - Timezone Aware)
exten => _X.,n,Set(TIMEZONE=${DB(vicidial/lead/${LEADID}/timezone)})
exten => _X.,n,GotoIf($["${TIMEZONE}" = "America/New_York"]?route_ny)
exten => _X.,n,GotoIf($["${TIMEZONE}" = "America/Los_Angeles"]?route_la)
exten => _X.,n,GotoIf($["${TIMEZONE}" = "America/Chicago"]?route_chi)
exten => _X.,n,Goto(route_default)
exten => _X.,n(route_ny),Set(OUTBOUND_ROUTE=NY_CARRIER)
exten => _X.,n,Set(OUTBOUND_TRUNK=SIP/ny-carrier-trunk)
exten => _X.,n,Goto(dial_lead)
exten => _X.,n(route_la),Set(OUTBOUND_ROUTE=LA_CARRIER)
exten => _X.,n,Set(OUTBOUND_TRUNK=SIP/la-carrier-trunk)
exten => _X.,n,Goto(dial_lead)
exten => _X.,n(route_chi),Set(OUTBOUND_ROUTE=CHI_CARRIER)
exten => _X.,n,Set(OUTBOUND_TRUNK=SIP/chi-carrier-trunk)
exten => _X.,n,Goto(dial_lead)
exten => _X.,n(route_default),Set(OUTBOUND_ROUTE=DEFAULT)
exten => _X.,n,Set(OUTBOUND_TRUNK=SIP/default-carrier)
exten => _X.,n,Goto(dial_lead)
exten => _X.,n(dial_lead),NoOp(Dialing ${EXTEN} via ${OUTBOUND_TRUNK})
exten => _X.,n,Dial(${OUTBOUND_TRUNK}/${EXTEN},45,tT)
exten => _X.,n,Hangup()
exten => h,1,NoOp(Call ended)
Reload Asterisk after changes:
sudo asterisk -rx "dialplan reload"
Implementing Do-Not-Call Hour Compliance
Set Compliant Calling Windows
The most critical use case is respecting local calling hour laws. Update campaigns to enforce compliance:
-- Sales campaign: 9 AM - 8 PM prospect time
UPDATE vicidial_campaigns
SET start_call_hour = 9,
end_call_hour = 20,
campaign_timezone = 'America/New_York',
lead_filter_level = 2,
allow_inbound_call_queue = '0'
WHERE campaign_id = 'SALES_USA';
-- Support campaign: 8 AM - 6 PM
UPDATE vicidial_campaigns
SET start_call_hour = 8,
end_call_hour = 18,
campaign_timezone = 'America/Chicago',
lead_filter_level = 2
WHERE campaign_id = 'SUPPORT_USA';
Validate Time Zone Logic
Run this query to identify leads that would be called outside allowed hours:
SELECT
vl.lead_id,
vl.phone_number,
vl.lead_timezone,
vc.start_call_hour,
vc.end_call_hour,
CONVERT_TZ(NOW(), @@session.time_zone, CONCAT('+00:00')) AS utc_now,
CONVERT_TZ(NOW(), @@session.time_zone, CONCAT(vl.lead_timezone)) AS prospect_local_time
FROM vicidial_list vl
JOIN vicidial_campaigns vc ON vl.campaign_id = vc.campaign_id
WHERE vl.campaign_id = 'SALES_USA'
AND vl.status = 'NEW'
LIMIT 20;
This identifies prospects and their current local times relative to calling hour restrictions.
Practical Example: Multi-Region Campaign
Complete Configuration Walkthrough
Scenario: Running a sales campaign across all US time zones with region-specific carriers.
1. Create Campaign with Timezone Settings
INSERT INTO vicidial_campaigns (
campaign_id,
campaign_name,
campaign_timezone,
start_call_hour,
end_call_hour,
lead_filter_level,
active,
allow_inbound_call_queue
) VALUES (
'MULTI_US',
'Multi-Region USA Sales',
'America/Chicago',
9,
20,
2,
'Y',
'N'
);
2. Assign Timezones to Leads
-- Batch assign by area code pattern
UPDATE vicidial_list SET lead_timezone = 'America/New_York'
WHERE phone_number REGEXP '^(201|202|203|212|215|516|718|845|914)'
AND campaign_id = 'MULTI_US';
UPDATE vicidial_list SET lead_timezone = 'America/Chicago'
WHERE phone_number REGEXP '^(217|312|618|630|708|773|815)'
AND campaign_id = 'MULTI_US';
UPDATE vicidial_list SET lead_timezone = 'America/Denver'
WHERE phone_number REGEXP '^(303|719|720|970)'
AND campaign_id = 'MULTI_US';
UPDATE vicidial_list SET lead_timezone = 'America/Los_Angeles'
WHERE phone_number REGEXP '^(209|213|310|408|415|510|619|650|707|714|760|805|818|916|949)'
AND campaign_id = 'MULTI_US';
3. Verify Distribution
SELECT
lead_timezone,
COUNT(*) AS lead_count,
MIN(phone_number) AS sample_number
FROM vicidial_list
WHERE campaign_id = 'MULTI_US'
GROUP BY lead_timezone
ORDER BY lead_timezone;
Expected output:
| lead_timezone | lead_count | sample_number |
|---------------------|------------|---------------|
| America/Chicago | 1250 | 3125551001 |
| America/Denver | 450 | 3035551234 |
| America/Los_Angeles | 2100 | 4155559876 |
| America/New_York | 1800 | 2125554321 |
Monitoring and Logging
Check Active Call Distribution by Timezone
Real-time view of which timezones are currently receiving calls:
SELECT
vl.lead_timezone,
COUNT(vcl.uniqueid) AS active_calls,
MIN(DATE_FORMAT(vcl.call_date, '%H:%i:%s')) AS earliest_call
FROM vicidial_closer_log vcl
JOIN vicidial_list vl ON vcl.lead_id = vl.lead_id
WHERE vcl.call_date >= DATE_SUB(NOW(), INTERVAL 1 HOUR)
AND vcl.campaign_id = 'MULTI_US'
GROUP BY vl.lead_timezone
ORDER BY active_calls DESC;
Audit Call Attempts Against Calling Hours
Identify any calls placed outside compliant windows:
SELECT
vl.lead_id,
vl.phone_number,
vl.lead_timezone,
vcl.call_date,
vc.start_call_hour,
vc.end_call_hour,
HOUR(CONVERT_TZ(vcl.call_date, @@session.time_zone, vl.lead_timezone)) AS call_hour_local,
CASE
WHEN HOUR(CONVERT_TZ(vcl.call_date, @@session.time_zone, vl.lead_timezone)) >= vc.start_call_hour
AND HOUR(CONVERT_TZ(vcl.call_date, @@session.time_zone, vl.lead_timezone)) < vc.end_call_hour
THEN 'COMPLIANT'
ELSE 'NON-COMPLIANT'
END AS compliance_status
FROM vicidial_closer_log vcl
JOIN vicidial_list vl ON vcl.lead_id = vl.lead_id
JOIN vicidial_campaigns vc ON vl.campaign_id = vc.campaign_id
WHERE vcl.campaign_id = 'MULTI_US'
AND vcl.call_date >= DATE_SUB(NOW(), INTERVAL 24 HOUR)
HAVING compliance_status = 'NON-COMPLIANT'
ORDER BY vcl.call_date DESC;
Enable Timezone Logging in Asterisk
Add verbose logging to track timezone decisions in the dialer:
Edit /etc/asterisk/logger.conf:
[logfiles]
messages => notice,warning,error,verbose(3)
Then view logs in real-time:
tail -f /var/log/asterisk/messages | grep -i timezone
Advanced: Custom Timezone Logic with AGI
For sophisticated routing beyond standard campaigns, implement an Asterisk Gateway Interface (AGI) script:
#!/usr/bin/perl
# /usr/share/astguiclient/agi-bin/timezone-router.agi
use strict;
use DBI;
use DateTime::TimeZone;
use DateTime;
my %ENV = ();
while(<STDIN>) {
chomp;
last unless length($_);
if (/^([\w\d_]*):\s?(.*)$/) {
$ENV{$1} = $2;
}
}
sub agi_output {
my $string = shift;
print STDOUT "$string\n";
warn "$string\n";
}
my $lead_id = $ARGV[0] // 0;
my $phone = $ARGV[1] // '';
# Connect to ViciDial database
my $dbh = DBI->connect('DBI:mysql:asterisk:localhost', 'cron', 'cron')
or die "Cannot connect: $DBI::errstr";
# Fetch lead timezone
my $sth = $dbh->prepare('SELECT lead_timezone FROM vicidial_list WHERE lead_id = ?');
$sth->execute($lead_id);
my ($tz_name) = $sth->fetchrow();
$sth->finish();
unless ($tz_name) {
agi_output("SET VARIABLE TIMEZONE_VALID 0");
$dbh->disconnect();
exit(0);
}
# Get current time in prospect timezone
my $tz = DateTime::TimeZone->new(name => $tz_name);
my $now = DateTime->now(time_zone => $tz);
my $hour = $now->hour();
my $dow = $now->day_of_week(); # 1=Monday, 7=Sunday
# Fetch campaign calling hours
$sth = $dbh->prepare('
SELECT start_call_hour, end_call_hour
FROM vicidial_campaigns
WHERE campaign_id = (SELECT campaign_id FROM vicidial_list WHERE lead_id = ?)
');
$sth->execute($lead_id);
my ($start_hour, $end_hour) = $sth->fetchrow();
$sth->finish();
# Check if within calling hours
my $can_call = 0;
if (defined $start_hour && defined $end_hour) {
$can_call = 1 if ($hour >= $start_hour && $hour < $end_hour);
}
agi_output("SET VARIABLE LEAD_TIMEZONE $tz_name");
agi_output("SET VARIABLE PROSPECT_LOCAL_HOUR $hour");
agi_output("SET VARIABLE CAN_CALL $can_call");
agi_output("SET VARIABLE CALL_START_HOUR $start_hour") if defined $start_hour;
$dbh->disconnect();
exit(0);
Execute from dialplan:
exten => _X.,1,AGI(agi-bin/timezone-router.agi,${LEADID},${EXTEN})
exten => _X.,n,GotoIf($[${CAN_CALL} = 1]?proceed:blocked)
exten => _X.,n(proceed),Dial(SIP/default-carrier/${EXTEN},45,tT)
exten => _X.,n(blocked),Busy()
Troubleshooting
Problem: Calls Being Placed Outside Calling Hours
Symptom: ViciDial is calling prospects outside configured windows.
Root Cause: lead_filter_level is set to 0 or timezone data is missing.
Solution:
# Check current filter level
mysql -u cron -pcron asterisk -e "
SELECT campaign_id, lead_filter_level
FROM vicidial_campaigns
WHERE campaign_id = 'TEST_CAMP';"
# Update to enforce filtering
mysql -u cron -pcron asterisk -e "
UPDATE vicidial_campaigns
SET lead_filter_level = 2
WHERE campaign_id = 'TEST_CAMP';"
# Verify all leads have timezone data
mysql -u cron -pcron asterisk -e "
SELECT COUNT(*) as leads_without_tz
FROM vicidial_list
WHERE campaign_id = 'TEST_CAMP'
AND lead_timezone IS NULL;"
If leads lack timezone, assign defaults:
UPDATE vicidial_list
SET lead_timezone = 'America/New_York'
WHERE campaign_id = 'TEST_CAMP'
AND lead_timezone IS NULL;
Problem: Dialer Skipping Too Many Leads
Symptom: Dialer queue moving slowly; many leads skipped.
Root Cause: Timezone filtering is too restrictive or timezone database is outdated.
Solution:
Check for daylight saving time transitions:
# List recent timezone changes
zdump -v /etc/localtime | grep "2024" | tail -5
Update timezone database if stale:
sudo apt-get update
sudo apt-get install --only-upgrade tzdata
sudo systemctl restart asterisk
Verify dialer is using correct server timezone:
# Check server timezone
timedatectl
# Verify MySQL timezone
mysql -u cron -pcron asterisk -e "SELECT @@session.time_zone, @@global.time_zone;"
Set explicit timezone in MySQL:
SET GLOBAL time_zone = 'UTC';
SET SESSION time_zone = 'UTC';
Problem: Inconsistent Results Between Predictions and Actual Call Times
Symptom: Query predicts calls should be blocked, but they're being placed anyway.
Root Cause: Server timezone differs from database timezone assumptions.
Solution:
Normalize to UTC in all queries:
SELECT
vl.lead_id,
vl.phone_number,
vl.lead_timezone,
vc.start_call_hour,
vc.end_call_hour,
-- Convert server time to UTC first, then to prospect timezone
CONVERT_TZ(NOW(), @@session.time_zone, 'UTC') AS utc_time,
DATE_FORMAT(
CONVERT_TZ(
CONVERT_TZ(NOW(), @@session.time_zone, 'UTC'),
'UTC',
vl.lead_timezone
),
'%H'
) AS prospect_hour_24
FROM vicidial_list vl
JOIN vicidial_campaigns vc ON vl.campaign_id = vc.campaign_id
WHERE vl.campaign_id = 'TEST_CAMP'
LIMIT 5;
Problem: Timezone in Asterisk Logs Shows as UTC, Not Prospect Time
Symptom: CDR timestamps don't reflect prospect local time.
Solution: This is expected behavior. ViciDial stores all times in server time (usually UTC) and converts during reporting. To see prospect time in logs, add a custom variable:
exten => _X.,1,Set(PROSPECT_TZ_TIME=${DB(vicidial/lead/${LEADID}/local_time)})
exten => _X.,n,NoOp(Calling ${EXTEN} in ${PROSPECT_TZ_TIME})
Problem: CONVERT_TZ() Returns NULL
Symptom: MySQL CONVERT_TZ function returning NULL in queries.
Root Cause: MySQL timezone tables not populated.
Solution:
# Load timezone data into MySQL
mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root -p mysql
# Restart MySQL
sudo systemctl restart mysql
# Verify load
mysql -u cron -pcron asterisk -e "SELECT COUNT(*) FROM mysql.time_zone;"
Should return a count > 500. If it's still NULL, ensure database has proper permissions:
sudo mysql_upgrade -u root -p
Summary
Implementing timezone routing in ViciDial requires attention to five key layers:
- Campaign Configuration — Set
campaign_timezone, calling hours, andlead_filter_levelto 2 - Lead Data — Populate
lead_timezoneaccurately via import or bulk update - Database Timezone — Ensure MySQL and server timezones are properly synchronized
- Dialplan Logic — Route calls through appropriate carriers and apply AGI scripts for complex rules
- Monitoring — Audit compliance and validate calls align with configured windows
Key takeaways:
- Always use
lead_filter_level = 2for production multi-timezone campaigns - Populate
lead_timezoneat import time; use area code patterns as fallback - Test timezone logic with direct SQL queries before enabling on live campaigns
- Monitor dialer logs hourly during daylight saving transitions
- Implement compliance audits weekly to catch misconfigured leads
With proper configuration, ViciDial's timezone routing increases answer rates by 15-25% while ensuring 100% compliance with calling hour regulations across all US time zones and international markets.