Setting Up Isolated Multi-Country Teams on a Single ViciDial Server
Campaigns, Inbound Groups, User Groups, SIP Peers, and Ring Group Fallbacks
Table of Contents
- Introduction
- Architecture Overview
- Planning: Naming Conventions and Number Ranges
- Step 1 -- Create User Groups
- Step 2 -- Create Campaigns
- Step 3 -- Create Inbound Groups
- Step 4 -- Create Phones (Bulk SQL)
- Step 5 -- Create SIP Peers in Asterisk
- Step 6 -- Create Agent User Accounts (Bulk SQL)
- Step 7 -- Assign Agents to Inbound Groups
- Step 8 -- Configure Ring Group Fallback Extensions
- Step 9 -- Route DIDs to Inbound Groups
- Step 10 -- Reload and Verify
- Common Pitfalls: 15 Issues We Found in Production
- Testing Checklist
- Monitoring Per-Country Performance
- Scaling to More Countries
- Quick Reference Card
1. Introduction
Why Team Isolation Matters
When a single ViciDial server handles calls for multiple countries -- say Country A, Country B, and Country C -- you need hard boundaries between each team. Without isolation:
- Agents see campaigns they should not work. A Country A agent could accidentally dial Country B leads with the wrong script, language, and caller ID.
- Inbound calls route to the wrong team. A caller dialing a Country B number could reach a Country A agent who does not speak the language.
- Reporting becomes unreliable. Campaign stats mix multiple countries together, making it impossible to measure per-country performance.
- After-hours calls disappear. Without per-country ring group fallbacks, calls that arrive when no agents are logged in have nowhere to go.
The solution is per-country isolation -- giving each country its own campaign, inbound group, user group, phone range, SIP peer range, and ring group fallback. This guide walks through every step, including the bulk SQL and Asterisk dialplan config that make it practical to deploy at scale.
What We Built
This tutorial is based on a real production deployment where we configured 3 country teams on a single ViciDial server:
| Component | Country A | Country B | Country C |
|---|---|---|---|
| Campaign | country_a |
country_b |
country_c |
| Inbound Group | country_a_ingr |
country_b_ingr |
country_c_ingr |
| User Group | COUNTRY_A |
COUNTRY_B |
COUNTRY_C |
| Phone Range | 2001-2090 | 3001-3090 | 4001-4090 |
| Agent Range | 2001-2030 | 3001-3030 | 4001-4030 |
| Ring Group | ringall_country_a |
ringall_country_b |
ringall_country_c |
Each country got 90 phones (30 active agents + 60 spare), 90 SIP peers, and a dedicated ring group with 40 phones for after-hours fallback.
Prerequisites
- A working ViciDial installation (ViciBox or manual install, version 2.14+)
- Admin access to the ViciDial web interface (user level 9)
- SSH root access to the server
- MySQL/MariaDB command-line access
- Basic familiarity with Asterisk dialplan syntax
- SIP trunks configured for each country's outbound dialing (or at minimum, one shared trunk)
2. Architecture Overview
┌─────────────────────────────────┐
│ ViciDial Server │
│ │
Country A DIDs ───────►│ Inbound Group: country_a_ingr │──► Country A Agents (2001-2030)
│ └─ fallback: ringall_country_a│──► Ring Group (2031-2070)
│ │
Country B DIDs ───────►│ Inbound Group: country_b_ingr │──► Country B Agents (3001-3030)
│ └─ fallback: ringall_country_b│──► Ring Group (3031-3070)
│ │
Country C DIDs ───────►│ Inbound Group: country_c_ingr │──► Country C Agents (4001-4030)
│ └─ fallback: ringall_country_c│──► Ring Group (4031-4070)
│ │
│ Campaign: country_a ◄──────────►│ User Group: COUNTRY_A
│ Campaign: country_b ◄──────────►│ User Group: COUNTRY_B
│ Campaign: country_c ◄──────────►│ User Group: COUNTRY_C
└─────────────────────────────────┘
Isolation Boundaries
Each country team is isolated by five independent mechanisms that reinforce each other:
- User Group -- Controls which campaigns and inbound groups an agent can see in the admin UI and agent interface.
- Campaign
allowed_campaigns-- Restricts which user groups can log into a campaign. - Inbound Group agent list -- Only agents explicitly added to an inbound group receive its calls.
- Phone
user_group-- Ties each SIP phone to a country, so phones appear in the correct admin filter. - Ring Group extension -- Each country has its own Asterisk dialplan extension that rings only that country's phones.
If any one boundary is misconfigured, the others still provide protection. But all five must be correct for clean isolation.
3. Planning: Naming Conventions and Number Ranges
Before touching the server, plan your naming scheme and number ranges on paper. Consistent naming prevents confusion as you scale.
Naming Convention
| Component | Pattern | Example |
|---|---|---|
| Campaign | {country} (lowercase) |
germany, france, spain |
| Inbound Group | {country}_ingr |
germany_ingr, france_ingr |
| User Group | {COUNTRY} (uppercase) |
GERMANY, FRANCE, SPAIN |
| Ring Group Extension | ringall_{country} |
ringall_germany, ringall_france |
Rules:
- Campaign names: max 8 characters in ViciDial. Use short country names or ISO codes (
de,fr,es) if you have many countries. - Inbound group IDs: max 20 characters. The
_ingrsuffix clearly distinguishes inbound groups from campaigns. - User groups: max 20 characters. Uppercase makes them visually distinct in the admin UI.
Number Range Allocation
Allocate non-overlapping ranges with room to grow. A good pattern:
| Country | Phone Start | Phone End | Agents | Ring Group Phones |
|---|---|---|---|---|
| Country A | 2001 | 2090 | 2001-2030 | 2031-2070 |
| Country B | 3001 | 3090 | 3001-3030 | 3031-3070 |
| Country C | 4001 | 4090 | 4001-4030 | 4031-4070 |
| Country D | 5001 | 5090 | 5001-5030 | 5031-5070 |
| (reserve) | 6001+ | ... | ... | ... |
Why 90 phones per country?
- 30 active agent phones (extensions x001-x030)
- 40 ring group phones (extensions x031-x070) -- registered on softphones at team leads' desks or mobile devices for after-hours coverage
- 20 spare phones (extensions x071-x090) -- for growth without reconfiguring
Why start at 2001, not 1001? Leave the 1001-1999 range for your existing/default agents. Do not renumber your current setup.
Step 1 -- Create User Groups
User groups are the foundation of ViciDial's permission system. Each country needs its own group.
Via the Admin UI
- Navigate to Admin > User Groups > Add a New User Group
- Fill in:
| Field | Value |
|---|---|
| User Group | COUNTRY_A |
| Group Name | Country A Team |
| Allowed Campaigns | country_a (add after creating the campaign) |
| Admin User Group | ---ALL--- (or your admin group) |
| Forced Timeclock Login | N |
| Group Level | 1 |
- Click Submit
- Repeat for
COUNTRY_BandCOUNTRY_C
Via SQL (Faster for Multiple Groups)
-- Create 3 user groups at once
INSERT INTO vicidial_user_groups (user_group, group_name, allowed_campaigns, forced_timeclock_login, group_level)
VALUES
('COUNTRY_A', 'Country A Team', ' country_a -', 'N', 1),
('COUNTRY_B', 'Country B Team', ' country_b -', 'N', 1),
('COUNTRY_C', 'Country C Team', ' country_c -', 'N', 1);
Note: The
allowed_campaignsfield uses a space-delimited format with leading and trailing markers:' campaign_name -'. The spaces and dash are required.
Step 2 -- Create Campaigns
Each country gets a dedicated outbound campaign. Even if a country only handles inbound calls, you still need a campaign because ViciDial agents must log into a campaign to receive inbound calls.
Via the Admin UI
- Navigate to Admin > Campaigns > Add a New Campaign
- Configure:
| Field | Value | Notes |
|---|---|---|
| Campaign ID | country_a |
Max 8 chars |
| Campaign Name | Country A |
Descriptive name |
| Campaign Description | Country A outbound/inbound |
|
| Dial Method | RATIO |
Or MANUAL if inbound-only |
| Auto Dial Level | 0 |
Set to 0 initially; increase when ready |
| Dial Prefix | X |
Placeholder; set to real prefix when trunks are ready |
| Campaign Allow Inbound | Y |
Critical -- allows agents to receive inbound while in this campaign |
| Campaign CID | (your country A outbound number) | |
| User Group | ---ALL--- |
Let the user_group restriction handle access |
- Under Detail View > Allowed Inbound Groups, add
country_a_ingr - Click Submit
Key Campaign Settings
After creation, go back into the campaign settings and verify:
| Setting | Recommended Value | Why |
|---|---|---|
campaign_allow_inbound |
Y |
Agents can take inbound calls |
dial_method |
RATIO or MANUAL |
RATIO for blended, MANUAL for inbound-only |
auto_dial_level |
0 (start here) |
No outbound until you're ready |
closer_campaigns |
country_a_ingr |
Which inbound groups feed into this campaign |
dispo_call_url |
(your CRM URL if applicable) | |
pause_codes |
FORCE |
Require agents to select a reason when pausing |
Repeat for country_b and country_c.
Step 3 -- Create Inbound Groups
Inbound groups (also called "closer groups") receive incoming calls and route them to available agents.
Via the Admin UI
- Navigate to Admin > Inbound Groups > Add a New Group
- Configure:
| Field | Value | Notes |
|---|---|---|
| Group ID | country_a_ingr |
Max 20 chars |
| Group Name | Country A Inbound |
|
| Group Color | #0066CC |
Pick a distinct color per country |
| Active | Y |
|
| Next Agent Call | inbound_group_rank |
Routes to highest-ranked available agent |
| Fronter Display | N |
|
| Call Time ID | 24hours |
Or your business hours scheme |
- Critical Inbound Group Settings:
| Setting | Value | Why |
|---|---|---|
no_agent_no_queue |
NO_READY |
Only queue calls when agents are in READY state |
no_agent_action |
EXTENSION |
When no agents are available, send call to a dialplan extension |
no_agent_action_value |
ringall_country_a,default |
The ring group extension (name,context) |
no_delay_call_route |
Y |
Route immediately, no artificial delay |
agent_choose_ingroups |
1 |
Agents can select this ingroup at login |
hold_time_option |
NONE |
No hold music or announcements |
hold_recall_xfer_group |
(empty) | |
onhold_prompt_filename |
NONE |
No audio prompts to callers |
welcome_message_filename |
---NONE--- |
No welcome message |
moh_context |
default |
Keep default but ensure no MOH files are configured |
play_welcome_message |
NEVER |
Never play a welcome message |
- Click Submit
The No-Agent Fallback (Critical)
The no_agent_action=EXTENSION setting is your safety net. When no agents are logged in or in READY state, ViciDial sends the call to the Asterisk dialplan extension you specify. This is where the ring group (configured in Step 8) catches the call and rings physical phones.
Without this setting, calls that arrive when no agents are logged in will be silently dropped. This is the single most important configuration for after-hours coverage.
The format for no_agent_action_value is extension_name,context -- for example, ringall_country_a,default.
Repeat for country_b_ingr and country_c_ingr.
Step 4 -- Create Phones (Bulk SQL)
Creating 90 phones per country through the admin UI would take hours. Use SQL instead.
Understanding the phones Table
Each row in the ViciDial phones table represents a SIP phone/extension. Key fields:
| Field | Purpose |
|---|---|
extension |
The SIP extension number (e.g., 2001) |
dialplan_number |
Usually same as extension |
server_ip |
Your ViciDial server IP |
login |
What the agent types to log in (usually same as extension) |
pass |
Agent login password |
server_phone_ring |
Ring behavior |
phone_context |
Asterisk context -- must be defaultlog |
user_group |
Which country group this phone belongs to |
conf_secret |
SIP registration password |
status |
Must be ACTIVE |
active_agent_login_server |
Your server IP |
on_hook_agent |
N for softphones |
Bulk Phone Creation Script
IMPORTANT: Replace
YOUR.SERVER.IPwith your actual ViciDial server IP address.
-- ============================================
-- COUNTRY A: Phones 2001-2090
-- ============================================
-- Generate 90 phone records for Country A
-- Using a number sequence joined approach
INSERT INTO phones (
extension, dialplan_number, voicemail_id, phone_ip, computer_ip,
server_ip, login, pass, status, active_agent_login_server,
phone_type, fullname, company, picture, messages, old_messages,
protocol, local_gmt, ASTmgrUSERNAME, ASTmgrSECRET, login_user,
login_pass, login_campaign, phone_ring_timeout, delete_vm_after_email,
is_webphone, use_external_server_ip, template_id, on_hook_agent,
active, phone_context, conf_secret, user_group, voicemail_dump_exten,
voicemail_timezone, server_phone_ring
)
SELECT
n.ext, n.ext, n.ext, '', '',
'YOUR.SERVER.IP', n.ext, 'SecurePass123', 'ACTIVE', 'YOUR.SERVER.IP',
'SIP', CONCAT('Country A Phone ', n.ext), '', '', 0, 0,
'SIP', '-5.00', '', '', '',
'', '', 60, 'N',
'N', '', '', 'N',
'Y', 'defaultlog', 'SecurePass123', 'COUNTRY_A', '', '', 'n'
FROM (
SELECT @row := @row + 1 AS ext
FROM information_schema.columns a,
information_schema.columns b,
(SELECT @row := 2000) r
LIMIT 90
) n
WHERE NOT EXISTS (SELECT 1 FROM phones WHERE extension = n.ext);
Repeat for Country B (change @row := 3000, COUNTRY_B) and Country C (change @row := 4000, COUNTRY_C).
Alternative: Loop Script
If the subquery approach does not work on your MariaDB version, use a shell loop:
#!/bin/bash
# create-phones.sh -- Bulk create phones for one country
# Usage: ./create-phones.sh <start> <end> <user_group> <server_ip> <password>
START=$1
END=$2
GROUP=$3
SERVER_IP=$4
PASS=$5
for EXT in $(seq $START $END); do
mysql -e "
INSERT IGNORE INTO phones (
extension, dialplan_number, voicemail_id, server_ip,
login, pass, status, active_agent_login_server,
phone_type, fullname, protocol, on_hook_agent,
active, phone_context, conf_secret, user_group, server_phone_ring
) VALUES (
'$EXT', '$EXT', '$EXT', '$SERVER_IP',
'$EXT', '$PASS', 'ACTIVE', '$SERVER_IP',
'SIP', 'Phone $EXT', 'SIP', 'N',
'Y', 'defaultlog', '$PASS', '$GROUP', 'n'
);
" asterisk
done
echo "Created phones $START-$END for group $GROUP"
Run it:
chmod +x create-phones.sh
./create-phones.sh 2001 2090 COUNTRY_A YOUR.SERVER.IP SecurePass123
./create-phones.sh 3001 3090 COUNTRY_B YOUR.SERVER.IP SecurePass123
./create-phones.sh 4001 4090 COUNTRY_C YOUR.SERVER.IP SecurePass123
Verify Phone Creation
-- Count phones per group
SELECT user_group, COUNT(*) AS phone_count,
MIN(extension) AS first_ext, MAX(extension) AS last_ext
FROM phones
WHERE user_group IN ('COUNTRY_A', 'COUNTRY_B', 'COUNTRY_C')
GROUP BY user_group;
-- Expected output:
-- +------------+-------------+-----------+----------+
-- | user_group | phone_count | first_ext | last_ext |
-- +------------+-------------+-----------+----------+
-- | COUNTRY_A | 90 | 2001 | 2090 |
-- | COUNTRY_B | 90 | 3001 | 3090 |
-- | COUNTRY_C | 90 | 4001 | 4090 |
-- +------------+-------------+-----------+----------+
Step 5 -- Create SIP Peers in Asterisk
ViciDial manages some SIP peers automatically, but when bulk-creating phones you must ensure matching SIP peer entries exist in Asterisk's configuration. Without SIP peers, softphones cannot register and calls cannot be bridged.
The SIP Peer Configuration File
ViciDial stores phone SIP peers in /etc/asterisk/sip-vicidial.conf. This file is auto-generated by ViciDial's processes, but when you bulk-create phones via SQL, the SIP peers may not be generated immediately. You have two options:
Option A: Wait for ViciDial to auto-generate -- ViciDial's AST_conf_update.pl process regenerates sip-vicidial.conf periodically. Restart the process or wait.
Option B: Manually append SIP peers -- Faster and more reliable for bulk operations.
SIP Peer Template
Each phone needs a SIP peer block in this exact format:
[2001]
username=2001
secret=SecurePass123
accountcode=2001
mailbox=2001
context=defaultlog
type=friend
host=dynamic
Bulk SIP Peer Generation Script
#!/bin/bash
# generate-sip-peers.sh -- Generate SIP peer blocks for a phone range
# Usage: ./generate-sip-peers.sh <start> <end> <secret> >> /etc/asterisk/sip-vicidial.conf
START=$1
END=$2
SECRET=$3
echo ""
echo "; ============================================"
echo "; SIP peers $START-$END (auto-generated)"
echo "; Generated: $(date '+%Y-%m-%d %H:%M:%S')"
echo "; ============================================"
for EXT in $(seq $START $END); do
cat <<EOF
[$EXT]
username=$EXT
secret=$SECRET
accountcode=$EXT
mailbox=$EXT
context=defaultlog
type=friend
host=dynamic
EOF
done
Run it:
chmod +x generate-sip-peers.sh
# ALWAYS back up first
cp /etc/asterisk/sip-vicidial.conf /etc/asterisk/sip-vicidial.conf.bak.$(date +%Y%m%d)
# Generate and append peers for each country
./generate-sip-peers.sh 2001 2090 SecurePass123 >> /etc/asterisk/sip-vicidial.conf
./generate-sip-peers.sh 3001 3090 SecurePass123 >> /etc/asterisk/sip-vicidial.conf
./generate-sip-peers.sh 4001 4090 SecurePass123 >> /etc/asterisk/sip-vicidial.conf
Reload Asterisk SIP Configuration
# Reload SIP config (does NOT drop active calls)
asterisk -rx "sip reload"
# Verify peers are loaded
asterisk -rx "sip show peers" | grep -c "2[0-9][0-9][0-9]" # Should show 90
asterisk -rx "sip show peers" | grep -c "3[0-9][0-9][0-9]" # Should show 90
asterisk -rx "sip show peers" | grep -c "4[0-9][0-9][0-9]" # Should show 90
Critical: The context Field
Every SIP peer must have context=defaultlog. This is the Asterisk context that ViciDial uses for agent phone registrations. If you set context=default (missing the "log" suffix), calls will still work but ViciDial's logging and monitoring features will malfunction.
This was one of the most common mistakes in our deployment -- see Pitfall #11 and Pitfall #13.
Step 6 -- Create Agent User Accounts (Bulk SQL)
Agents need ViciDial user accounts to log in. Each account must be tied to the correct user group.
Key Fields in vicidial_users
| Field | Purpose | Correct Value |
|---|---|---|
user |
Agent login ID | Extension number (e.g., 2001) |
pass |
Login password | Strong, unique per deployment |
full_name |
Display name | Agent's real name or placeholder |
user_level |
Permission level | 1 for agents, 9 for admins |
user_group |
Country group | COUNTRY_A, COUNTRY_B, etc. |
active |
Account active? | Y |
agent_choose_ingroups |
Can agent select ingroups at login? | 1 (yes) |
Bulk User Creation Script
#!/bin/bash
# create-agents.sh -- Bulk create agent accounts
# Usage: ./create-agents.sh <start> <end> <user_group> <password>
START=$1
END=$2
GROUP=$3
PASS=$4
for ID in $(seq $START $END); do
mysql -e "
INSERT IGNORE INTO vicidial_users (
user, pass, full_name, user_level, user_group,
active, phone_login, phone_pass,
agent_choose_ingroups, closer_default_blended
) VALUES (
'$ID', '$PASS', 'Agent $ID', 1, '$GROUP',
'Y', '$ID', '$PASS',
1, 1
);
" asterisk
done
echo "Created agents $START-$END for group $GROUP"
Run it:
./create-agents.sh 2001 2030 COUNTRY_A SecurePass123
./create-agents.sh 3001 3030 COUNTRY_B SecurePass123
./create-agents.sh 4001 4030 COUNTRY_C SecurePass123
Verify User Creation
SELECT user_group, COUNT(*) AS agent_count,
MIN(user) AS first_agent, MAX(user) AS last_agent
FROM vicidial_users
WHERE user_group IN ('COUNTRY_A', 'COUNTRY_B', 'COUNTRY_C')
AND user_level = 1
GROUP BY user_group;
Update Existing Users' Group Assignment
If agents already exist and you need to move them to the correct group:
-- Move agents 2001-2030 to COUNTRY_A
UPDATE vicidial_users
SET user_group = 'COUNTRY_A'
WHERE user BETWEEN '2001' AND '2030'
AND user_level = 1;
-- Move agents 3001-3030 to COUNTRY_B
UPDATE vicidial_users
SET user_group = 'COUNTRY_B'
WHERE user BETWEEN '3001' AND '3030'
AND user_level = 1;
-- Move agents 4001-4030 to COUNTRY_C
UPDATE vicidial_users
SET user_group = 'COUNTRY_C'
WHERE user BETWEEN '4001' AND '4030'
AND user_level = 1;
Important: Also set
agent_choose_ingroups = 1for all agents. Without this, agents cannot select their country's inbound group at login and will not receive any inbound calls:UPDATE vicidial_users SET agent_choose_ingroups = 1 WHERE user_group IN ('COUNTRY_A', 'COUNTRY_B', 'COUNTRY_C');
Step 7 -- Assign Agents to Inbound Groups
Each agent must be explicitly added to their country's inbound group. This is what controls which agents receive which inbound calls.
Via the Admin UI
- Go to Admin > Inbound Groups > click on
country_a_ingr - Click the Agents link (or scroll to the agent assignment section)
- Add each Country A agent (2001-2030) to the group
- Set their rank (1-9, where 9 is highest priority)
- Repeat for
country_b_ingr(agents 3001-3030) andcountry_c_ingr(agents 4001-4030)
Via SQL (Much Faster)
-- Assign Country A agents to country_a_ingr
INSERT IGNORE INTO vicidial_inbound_group_agents (group_id, user, group_rank, group_weight, calls_today, group_type)
SELECT 'country_a_ingr', user, 5, 0, 0, 'C'
FROM vicidial_users
WHERE user_group = 'COUNTRY_A' AND user_level = 1 AND active = 'Y';
-- Assign Country B agents to country_b_ingr
INSERT IGNORE INTO vicidial_inbound_group_agents (group_id, user, group_rank, group_weight, calls_today, group_type)
SELECT 'country_b_ingr', user, 5, 0, 0, 'C'
FROM vicidial_users
WHERE user_group = 'COUNTRY_B' AND user_level = 1 AND active = 'Y';
-- Assign Country C agents to country_c_ingr
INSERT IGNORE INTO vicidial_inbound_group_agents (group_id, user, group_rank, group_weight, calls_today, group_type)
SELECT 'country_c_ingr', user, 5, 0, 0, 'C'
FROM vicidial_users
WHERE user_group = 'COUNTRY_C' AND user_level = 1 AND active = 'Y';
Add Your Admin Account to All Inbound Groups
Your admin user should be in every inbound group for testing and emergency coverage:
-- Add admin (user 6666) to all 3 inbound groups
INSERT IGNORE INTO vicidial_inbound_group_agents (group_id, user, group_rank, group_weight, calls_today, group_type)
VALUES
('country_a_ingr', '6666', 1, 0, 0, 'C'),
('country_b_ingr', '6666', 1, 0, 0, 'C'),
('country_c_ingr', '6666', 1, 0, 0, 'C');
Tip: Give the admin account a low rank (1) so real agents get calls first. The admin is a safety net, not a primary agent.
Verify Agent Assignments
-- Check agent counts per inbound group
SELECT group_id, COUNT(*) AS agent_count
FROM vicidial_inbound_group_agents
WHERE group_id IN ('country_a_ingr', 'country_b_ingr', 'country_c_ingr')
GROUP BY group_id;
-- Check for cross-contamination (agents in wrong groups)
SELECT a.group_id, a.user, u.user_group
FROM vicidial_inbound_group_agents a
JOIN vicidial_users u ON a.user = u.user
WHERE a.group_id LIKE '%_ingr'
AND a.group_id NOT LIKE CONCAT(LOWER(u.user_group), '%')
AND u.user_group NOT IN ('---ALL---', 'ADMIN');
-- This query should return ZERO rows. Any results indicate cross-assignment.
Step 8 -- Configure Ring Group Fallback Extensions
Ring groups are Asterisk dialplan extensions that ring multiple phones simultaneously. They are the safety net for when no ViciDial agents are available (after hours, breaks, lunch, or if ViciDial processes go down).
How It Works
- A call arrives for Country A
- ViciDial's inbound group
country_a_ingrlooks for an available agent - No agent is available (
no_agent_no_queue=NO_READY) - ViciDial executes
no_agent_action=EXTENSIONwith valueringall_country_a,default - The call hits the Asterisk dialplan extension
ringall_country_ain context[default] - The extension rings 40 phones simultaneously, retrying 25 times with 4-second timeout
- If nobody answers after all retries, the call is hung up
The Ring Group Dialplan File
Create a dedicated file for ring group extensions. This keeps them separate from ViciDial's auto-generated dialplan.
File: /etc/asterisk/ringall.conf
First, ensure this file is included in Asterisk's dialplan. Check /etc/asterisk/extensions.conf for an #include directive, or add one:
; At the end of /etc/asterisk/extensions.conf, in the [default] context:
#include ringall.conf
Ring Group Configuration
; /etc/asterisk/ringall.conf
; Ring group fallback extensions for country team isolation
; Each group: 40 phones, 4s ring timeout, 25 retries, then Hangup
[default]
; ============================================
; Country A ring group -- phones 2031-2070
; ============================================
exten => ringall_country_a,1,Dial(SIP/2031&SIP/2032&SIP/2033&SIP/2034&SIP/2035&SIP/2036&SIP/2037&SIP/2038&SIP/2039&SIP/2040&SIP/2041&SIP/2042&SIP/2043&SIP/2044&SIP/2045&SIP/2046&SIP/2047&SIP/2048&SIP/2049&SIP/2050&SIP/2051&SIP/2052&SIP/2053&SIP/2054&SIP/2055&SIP/2056&SIP/2057&SIP/2058&SIP/2059&SIP/2060&SIP/2061&SIP/2062&SIP/2063&SIP/2064&SIP/2065&SIP/2066&SIP/2067&SIP/2068&SIP/2069&SIP/2070,4,tTo)
same => n,Dial(SIP/2031&SIP/2032&SIP/2033&SIP/2034&SIP/2035&SIP/2036&SIP/2037&SIP/2038&SIP/2039&SIP/2040&SIP/2041&SIP/2042&SIP/2043&SIP/2044&SIP/2045&SIP/2046&SIP/2047&SIP/2048&SIP/2049&SIP/2050&SIP/2051&SIP/2052&SIP/2053&SIP/2054&SIP/2055&SIP/2056&SIP/2057&SIP/2058&SIP/2059&SIP/2060&SIP/2061&SIP/2062&SIP/2063&SIP/2064&SIP/2065&SIP/2066&SIP/2067&SIP/2068&SIP/2069&SIP/2070,4,tTo)
same => n,Dial(SIP/2031&SIP/2032&SIP/2033&SIP/2034&SIP/2035&SIP/2036&SIP/2037&SIP/2038&SIP/2039&SIP/2040&SIP/2041&SIP/2042&SIP/2043&SIP/2044&SIP/2045&SIP/2046&SIP/2047&SIP/2048&SIP/2049&SIP/2050&SIP/2051&SIP/2052&SIP/2053&SIP/2054&SIP/2055&SIP/2056&SIP/2057&SIP/2058&SIP/2059&SIP/2060&SIP/2061&SIP/2062&SIP/2063&SIP/2064&SIP/2065&SIP/2066&SIP/2067&SIP/2068&SIP/2069&SIP/2070,4,tTo)
; ... repeat the "same => n,Dial(...)" line 25 times total for 25 retries ...
same => n,Hangup()
Generating Ring Groups with a Script
Writing 25 Dial() lines by hand for 40 phones is tedious and error-prone. Use this script:
#!/bin/bash
# generate-ringall.sh -- Generate ring group dialplan
# Usage: ./generate-ringall.sh <ext_name> <start> <end> <timeout> <retries>
# Example: ./generate-ringall.sh ringall_country_a 2031 2070 4 25
EXT_NAME=$1
START=$2
END=$3
TIMEOUT=$4
RETRIES=$5
# Build the dial string (SIP/2031&SIP/2032&...&SIP/2070)
DIAL_STRING=""
for PHONE in $(seq $START $END); do
if [ -z "$DIAL_STRING" ]; then
DIAL_STRING="SIP/$PHONE"
else
DIAL_STRING="$DIAL_STRING&SIP/$PHONE"
fi
done
echo "; $(echo $EXT_NAME | tr '_' ' ') -- phones $START-$END, ${TIMEOUT}s timeout, $RETRIES retries"
echo "exten => $EXT_NAME,1,Dial($DIAL_STRING,$TIMEOUT,tTo)"
for i in $(seq 2 $RETRIES); do
echo "same => n,Dial($DIAL_STRING,$TIMEOUT,tTo)"
done
echo "same => n,Hangup()"
echo ""
Run it:
chmod +x generate-ringall.sh
# Create the ring group config file
echo "[default]" > /etc/asterisk/ringall.conf
echo "" >> /etc/asterisk/ringall.conf
./generate-ringall.sh ringall_country_a 2031 2070 4 25 >> /etc/asterisk/ringall.conf
./generate-ringall.sh ringall_country_b 3031 3070 4 25 >> /etc/asterisk/ringall.conf
./generate-ringall.sh ringall_country_c 4031 4070 4 25 >> /etc/asterisk/ringall.conf
Dial() Parameters Explained
In Dial(SIP/2031&SIP/2032&...,4,tTo):
| Parameter | Meaning |
|---|---|
SIP/2031&SIP/2032&... |
Ring all listed phones simultaneously |
4 |
Ring for 4 seconds per attempt |
t |
Allow the called party to transfer the call |
T |
Allow the calling party to transfer the call |
o |
Use the caller's original caller ID |
Why 4 Seconds and 25 Retries?
- 4 seconds is short enough that if no phones are registered (off-hours), each retry fails quickly. The caller hears ringback during retries.
- 25 retries x 4 seconds = up to 100 seconds of ringing, which is about 1.5 minutes. This gives on-call staff enough time to notice and answer.
- After 25 retries,
Hangup()cleanly terminates the call. The caller hears the call end.
Important: Include the Ring Group File
Make sure /etc/asterisk/ringall.conf is included from Asterisk's main dialplan. Check:
grep -r "ringall" /etc/asterisk/extensions.conf /etc/asterisk/extensions-vicidial.conf
If not included, add it to the [default] context in /etc/asterisk/extensions.conf:
#include ringall.conf
Or include the ring group extensions directly in /etc/asterisk/customexte.conf if your ViciDial version uses that file for custom extensions (ViciDial will not overwrite this file).
Step 9 -- Route DIDs to Inbound Groups
Each country's phone numbers (DIDs) must be routed to the correct inbound group.
Via the Admin UI
- Go to Admin > Inbound DIDs
- Click Add a New DID
- Configure:
| Field | Value |
|---|---|
| DID Extension | The full phone number (e.g., 441234567890) |
| DID Route | IN_GROUP |
| Group ID | country_a_ingr |
| Active | Y |
- Repeat for every DID for that country
Via SQL (Bulk DID Routing)
-- Route a batch of DIDs to Country A's inbound group
INSERT INTO vicidial_inbound_dids (did_pattern, did_description, did_active, did_route, group_id)
VALUES
('441234567001', 'Country A DID 1', 'Y', 'IN_GROUP', 'country_a_ingr'),
('441234567002', 'Country A DID 2', 'Y', 'IN_GROUP', 'country_a_ingr'),
('441234567003', 'Country A DID 3', 'Y', 'IN_GROUP', 'country_a_ingr');
-- Bulk-update existing DIDs to a different inbound group
UPDATE vicidial_inbound_dids
SET group_id = 'country_b_ingr'
WHERE did_pattern IN ('341234567001', '341234567002', '341234567003');
Step 10 -- Reload and Verify
After all configuration changes, reload the relevant services.
Asterisk Reload
# Reload SIP peers (new phone registrations)
asterisk -rx "sip reload"
# Reload dialplan (ring group extensions)
asterisk -rx "dialplan reload"
# Verify SIP peers are loaded
asterisk -rx "sip show peers" | tail -5
# Look for the total count -- should include your new peers
# Verify dialplan extensions exist
asterisk -rx "dialplan show ringall_country_a@default"
asterisk -rx "dialplan show ringall_country_b@default"
asterisk -rx "dialplan show ringall_country_c@default"
ViciDial Process Check
ViciDial's background processes need to pick up the new configuration:
# Check ViciDial screen sessions are running
screen -ls
# If needed, restart the keeper process (this restarts all ViciDial processes)
/usr/share/astguiclient/ADMIN_keepalive_ALL.pl --cu3way
Quick Verification SQL
-- Summary of the entire setup
SELECT 'Phones' AS component,
user_group AS country,
COUNT(*) AS count
FROM phones
WHERE user_group IN ('COUNTRY_A', 'COUNTRY_B', 'COUNTRY_C')
GROUP BY user_group
UNION ALL
SELECT 'Agents', user_group, COUNT(*)
FROM vicidial_users
WHERE user_group IN ('COUNTRY_A', 'COUNTRY_B', 'COUNTRY_C')
AND user_level = 1
GROUP BY user_group
UNION ALL
SELECT 'Ingroup Agents', group_id, COUNT(*)
FROM vicidial_inbound_group_agents
WHERE group_id IN ('country_a_ingr', 'country_b_ingr', 'country_c_ingr')
GROUP BY group_id
UNION ALL
SELECT 'DIDs', group_id, COUNT(*)
FROM vicidial_inbound_dids
WHERE group_id IN ('country_a_ingr', 'country_b_ingr', 'country_c_ingr')
GROUP BY group_id;
Common Pitfalls: 15 Issues We Found in Production
During our production deployment, we encountered 15 distinct configuration issues that caused problems ranging from agents unable to log in to calls being silently dropped. Every one of these was discovered only through testing.
Pitfall #1: Agent Passwords Set to User ID Instead of Intended Password
Symptom: Agents cannot log in with the documented password.
Root Cause: When bulk-creating users via SQL, a copy-paste error set pass = user instead of pass = 'SecurePass123'.
Fix:
UPDATE vicidial_users
SET pass = 'SecurePass123'
WHERE user_group = 'COUNTRY_A' AND user BETWEEN '2002' AND '2030';
Prevention: Always verify with a SELECT after bulk INSERT:
SELECT user, pass FROM vicidial_users WHERE user_group = 'COUNTRY_A' LIMIT 5;
Pitfall #2: Wrong Agents Assigned to an Inbound Group
Symptom: Country B inbound calls route to Country A agents.
Root Cause: When populating vicidial_inbound_group_agents for country_b_ingr, the wrong user range was specified -- Country A agents (2001-2030) were inserted instead of Country B agents (3001-3030).
Fix:
-- Remove wrong agents
DELETE FROM vicidial_inbound_group_agents
WHERE group_id = 'country_b_ingr' AND user BETWEEN '2001' AND '2030';
-- Add correct agents
INSERT IGNORE INTO vicidial_inbound_group_agents (group_id, user, group_rank, group_weight, calls_today, group_type)
SELECT 'country_b_ingr', user, 5, 0, 0, 'C'
FROM vicidial_users WHERE user_group = 'COUNTRY_B' AND user_level = 1;
Prevention: Always use a JOIN against vicidial_users filtered by user_group rather than hardcoding user ranges.
Pitfall #3: ALL Agents Assigned to an Inbound Group Instead of Just One Country
Symptom: Country C inbound group has 90 agents instead of 30. Calls for Country C may route to agents from any country.
Root Cause: The INSERT for country_c_ingr selected all agents with user_level = 1 without filtering by user_group.
Fix:
-- Remove all, then re-add only the correct country
DELETE FROM vicidial_inbound_group_agents WHERE group_id = 'country_c_ingr';
INSERT IGNORE INTO vicidial_inbound_group_agents (group_id, user, group_rank, group_weight, calls_today, group_type)
SELECT 'country_c_ingr', user, 5, 0, 0, 'C'
FROM vicidial_users WHERE user_group = 'COUNTRY_C' AND user_level = 1;
Pitfall #4: Phone conf_secret Set to a Placeholder Value
Symptom: Softphone registers but audio is one-way or agent screen shows errors.
Root Cause: Phone records had conf_secret = 'test' instead of the actual SIP password, likely from a template that was not updated.
Fix:
UPDATE phones
SET conf_secret = 'SecurePass123'
WHERE user_group IN ('COUNTRY_B', 'COUNTRY_C')
AND conf_secret = 'test';
Pitfall #5: Phone Status is NULL Instead of ACTIVE
Symptom: Phones do not appear in ViciDial's phone list. Agents cannot select them at login.
Root Cause: Bulk SQL INSERT did not specify the status field, which defaulted to NULL.
Fix:
UPDATE phones
SET status = 'ACTIVE'
WHERE user_group IN ('COUNTRY_B', 'COUNTRY_C')
AND (status IS NULL OR status = '');
Pitfall #6: Phone Login Has Wrong Format (Suffix Characters)
Symptom: Agent types 2001 as phone login but ViciDial expects 2001a (or vice versa).
Root Cause: ViciDial's UI sometimes appends a letter suffix (like a) to phone logins. Bulk SQL creation may or may not match this convention.
Fix: Decide on a convention and enforce it:
-- Remove any suffix (make login = extension number only)
UPDATE phones
SET login = extension
WHERE user_group IN ('COUNTRY_A', 'COUNTRY_B', 'COUNTRY_C')
AND login != extension;
Tip: Using the plain extension number as the login is simpler for agents and avoids this issue entirely.
Pitfall #7: Inbound Group no_agent_action_value Has Wrong Format
Symptom: When no agents are available, calls are dropped instead of being sent to the ring group.
Root Cause: The no_agent_action_value was set to just ringall_country_a but ViciDial expects the format extension_name,context -- specifically ringall_country_a,default.
Fix:
UPDATE vicidial_inbound_groups
SET no_agent_action_value = 'ringall_country_a,default'
WHERE group_id = 'country_a_ingr';
The format is: extension_name,asterisk_context (comma-separated, no spaces).
Pitfall #8: Music on Hold and Hold Prompts Configured on Inbound Groups
Symptom: Callers hear hold music or recorded messages while waiting, which may not be desired.
Root Cause: ViciDial defaults to playing hold music. If your requirement is that callers should only hear normal ringing (as if calling a regular business), you must explicitly disable all audio prompts.
Fix:
UPDATE vicidial_inbound_groups
SET hold_time_option = 'NONE',
onhold_prompt_filename = 'NONE',
welcome_message_filename = '---NONE---',
play_welcome_message = 'NEVER',
hold_time_option_seconds = 0,
hold_time_option_exten = '',
hold_recall_xfer_group = '',
hold_time_option_callback = 'NONE'
WHERE group_id IN ('country_a_ingr', 'country_b_ingr', 'country_c_ingr');
Pitfall #9: Ring Group Has Too Few Phones, Long Timeout, and Only 1 Retry
Symptom: After-hours calls ring briefly on a few phones and then disconnect.
Root Cause: Initial ring group config had only 4 phones, 20-second timeout, and 1 retry. This means:
- Only 4 phones ring (not enough coverage)
- 20 seconds is too long for a single attempt (caller waits, no one answers)
- 1 retry = only 2 total attempts = 40 seconds maximum
Fix: Rebuild with 40 phones, 4-second timeout, 25 retries:
# Regenerate the ring group (see Step 8 script)
./generate-ringall.sh ringall_country_a 2031 2070 4 25 > /tmp/ringall_a.txt
# Replace the old extension in ringall.conf with the new one
Rationale:
- 40 phones = wide coverage (all team leads and on-call staff)
- 4 seconds = fast retry, caller hears continuous ringing
- 25 retries = ~100 seconds of ringing before giving up
Pitfall #10: Phone user_group Set to ---ALL--- Instead of Country Group
Symptom: All phones appear under every country in admin filters. No isolation in the phone management view.
Root Cause: Bulk phone creation did not set user_group, which defaults to ---ALL---.
Fix:
UPDATE phones SET user_group = 'COUNTRY_B'
WHERE extension BETWEEN '3001' AND '3090' AND user_group = '---ALL---';
UPDATE phones SET user_group = 'COUNTRY_C'
WHERE extension BETWEEN '4001' AND '4090' AND user_group = '---ALL---';
Pitfall #11: Phone Context Set to default Instead of defaultlog
Symptom: Agents can make and receive calls, but ViciDial's call logging and agent monitoring may not work correctly. The admin Realtime report may not show the agent's calls.
Root Cause: The phone_context field in the phones table was set to default instead of defaultlog. ViciDial uses the defaultlog context to track agent activity.
Fix:
UPDATE phones
SET phone_context = 'defaultlog'
WHERE phone_context = 'default'
AND user_group IN ('COUNTRY_A', 'COUNTRY_B', 'COUNTRY_C');
Pitfall #12: SIP Peers Missing for Two Countries
Symptom: Softphones for Country B and Country C agents cannot register. Asterisk logs show NOTICE: Registration from 'SIP/3001' failed -- No matching peer found.
Root Cause: Only Country A's SIP peers were in sip-vicidial.conf. Country B and Country C peers were never added -- the ViciDial auto-generation did not pick up the bulk-created phones.
Fix: Generate and append the missing SIP peers:
./generate-sip-peers.sh 3001 3090 SecurePass123 >> /etc/asterisk/sip-vicidial.conf
./generate-sip-peers.sh 4001 4090 SecurePass123 >> /etc/asterisk/sip-vicidial.conf
asterisk -rx "sip reload"
This is one of the most critical and most commonly missed steps. Always verify SIP peers exist for every phone you create.
Pitfall #13: SIP Peer Context Was default, Not defaultlog
Symptom: Same as Pitfall #11, but at the Asterisk level. Even if the ViciDial phones table is correct, the SIP peer config overrides it.
Root Cause: The SIP peer blocks in sip-vicidial.conf had context=default instead of context=defaultlog.
Fix:
# Fix all peers in the file (carefully -- do not change trunk contexts)
# Only change context for phone peers (friend type, dynamic host)
sed -i '/^context=default$/s/default/defaultlog/' /etc/asterisk/sip-vicidial.conf
Warning: The sed command above is aggressive. A safer approach is to regenerate the SIP peers with the correct context using the generation script. Always back up first.
Pitfall #14: agent_choose_ingroups Was 0 for Two Countries
Symptom: Agents log into the campaign but the inbound group selection screen does not appear. They never receive inbound calls because they are not subscribed to any inbound group.
Root Cause: The user accounts were created with agent_choose_ingroups = 0 (the default for some ViciDial versions). This prevents the agent from selecting inbound groups at login.
Fix:
UPDATE vicidial_users
SET agent_choose_ingroups = 1
WHERE user_group IN ('COUNTRY_B', 'COUNTRY_C');
Pitfall #15: Outbound Caller ID Inconsistent (NULL vs Empty vs Zeros)
Symptom: Outbound calls show random or invalid caller IDs. Some calls fail because the trunk rejects the CID.
Root Cause: Some user accounts had outbound_cid = NULL, others had outbound_cid = '0000000000', and others had it empty. ViciDial handles these differently depending on the campaign and trunk configuration.
Fix: Normalize to empty string (lets the campaign-level CID take effect):
UPDATE vicidial_users
SET outbound_cid = ''
WHERE user_group IN ('COUNTRY_A', 'COUNTRY_B', 'COUNTRY_C')
AND (outbound_cid IS NULL OR outbound_cid = '0000000000');
Testing Checklist
Run through this checklist for every country before going live.
Agent Login Tests
| # | Test | Expected Result | Pass? |
|---|---|---|---|
| 1 | Agent logs in with phone extension and password | Login succeeds, agent screen loads | |
| 2 | Agent sees ONLY their country's campaign in the campaign dropdown | No other countries' campaigns visible | |
| 3 | After campaign login, ingroup selection screen appears | Agent can select their country's inbound group | |
| 4 | Agent selects their inbound group and clicks Ready | Agent status shows READY in the Realtime report | |
| 5 | Agent sees ONLY their country's inbound group in the selection list | Other countries' ingroups not visible |
Inbound Call Tests
| # | Test | Expected Result | Pass? |
|---|---|---|---|
| 6 | Call a Country A DID while a Country A agent is READY | Call routes to the Country A agent | |
| 7 | Call a Country A DID while NO Country A agents are READY (but Country B agents are READY) | Call goes to ring group, NOT to Country B agent | |
| 8 | Call a Country A DID while no agents at all are logged in | Call goes to Country A ring group (phones ring) | |
| 9 | Answer the ring group call on a ring group phone | Call connects with two-way audio | |
| 10 | Let the ring group exhaust all retries | Call is hung up cleanly (no stuck channels) |
SIP Registration Tests
| # | Test | Expected Result | Pass? |
|---|---|---|---|
| 11 | Register a softphone with extension 2001 | asterisk -rx "sip show peer 2001" shows REGISTERED |
|
| 12 | Register a softphone with extension 3001 | asterisk -rx "sip show peer 3001" shows REGISTERED |
|
| 13 | Register a softphone with extension 4001 | asterisk -rx "sip show peer 4001" shows REGISTERED |
Admin Verification
| # | Test | Expected Result | Pass? |
|---|---|---|---|
| 14 | Check Realtime report for agent groups | Agents appear under correct campaigns | |
| 15 | Check inbound group stats | Calls counted under correct inbound group | |
| 16 | Filter phones by user group in Admin > Phones | Each country shows only its phones |
CLI Verification Commands
# Check SIP peer count per range
echo "Country A peers: $(asterisk -rx 'sip show peers' | grep -c '^2[0-9]\{3\}')"
echo "Country B peers: $(asterisk -rx 'sip show peers' | grep -c '^3[0-9]\{3\}')"
echo "Country C peers: $(asterisk -rx 'sip show peers' | grep -c '^4[0-9]\{3\}')"
# Check ring group dialplan exists
asterisk -rx "dialplan show ringall_country_a@default" | head -3
asterisk -rx "dialplan show ringall_country_b@default" | head -3
asterisk -rx "dialplan show ringall_country_c@default" | head -3
# Check active channels during a test call
asterisk -rx "core show channels verbose"
Monitoring Per-Country Performance
ViciDial Built-In Reports
ViciDial's reports can filter by campaign and inbound group, giving you per-country views:
| Report | URL Path | Use For |
|---|---|---|
| Agent Performance Detail | AST_agent_performance_detail.php |
Per-agent calls, talk time, pause time |
| Agent Time Detail | AST_agent_time_detail.php |
Agent time breakdown by status |
| Closer Stats | AST_CLOSERstats.php |
Inbound group call volume and answer rates |
| Campaign Stats | AST_VDADstats.php |
Outbound campaign performance |
Filter by campaign or inbound group to see per-country data. For example, AST_CLOSERstats.php with group=country_a_ingr shows only Country A inbound stats.
SQL Queries for Per-Country Metrics
-- Calls per country today (inbound)
SELECT group_id AS country_group,
COUNT(*) AS total_calls,
SUM(CASE WHEN status = 'SALE' THEN 1 ELSE 0 END) AS sales,
AVG(length_in_sec) AS avg_talk_seconds
FROM vicidial_closer_log
WHERE call_date >= CURDATE()
AND group_id IN ('country_a_ingr', 'country_b_ingr', 'country_c_ingr')
GROUP BY group_id;
-- Agents logged in per country right now
SELECT u.user_group AS country,
COUNT(*) AS agents_logged_in,
SUM(CASE WHEN la.status = 'READY' THEN 1 ELSE 0 END) AS agents_ready
FROM vicidial_live_agents la
JOIN vicidial_users u ON la.user = u.user
WHERE u.user_group IN ('COUNTRY_A', 'COUNTRY_B', 'COUNTRY_C')
GROUP BY u.user_group;
Grafana Dashboard (Optional)
If you have Grafana connected to your ViciDial database (via a read-only replica), create a dashboard with:
- Panel 1: Calls per inbound group (time series)
- Panel 2: Active agents per user group (gauge)
- Panel 3: Answer rate by inbound group (stat)
- Panel 4: Average hold time by inbound group (stat)
Use the user_group or campaign_id field as the Grafana variable to switch between countries.
Scaling to More Countries
Adding a 4th Country
When you need to add Country D, follow this condensed checklist:
- Choose a number range: 5001-5090 (next available block)
- Create user group:
COUNTRY_Dvia SQL - Create campaign:
country_dwithcampaign_allow_inbound=Y - Create inbound group:
country_d_ingrwithno_agent_action=EXTENSION, valueringall_country_d,default - Create 90 phones: SQL INSERT with
user_group=COUNTRY_D,phone_context=defaultlog - Create 90 SIP peers: Append to
sip-vicidial.conf,asterisk -rx "sip reload" - Create agent accounts: SQL INSERT with
user_group=COUNTRY_D,agent_choose_ingroups=1 - Assign agents to inbound group: SQL INSERT into
vicidial_inbound_group_agents - Create ring group: Append to
ringall.conf,asterisk -rx "dialplan reload" - Route DIDs: Point Country D DIDs to
country_d_ingr - Test: Run through the full testing checklist
Time estimate: 30-45 minutes for an experienced admin using the SQL scripts in this guide.
Scaling Limits
| Factor | Practical Limit | Notes |
|---|---|---|
| Countries per server | 10-15 | Limited by Asterisk SIP peer count and memory |
| SIP peers per server | ~2,000 | Beyond this, consider a separate Asterisk proxy |
| Agents per server | ~500 | Depends on call volume and server hardware |
| Ring group phones | ~50 per group | Asterisk Dial() has a practical limit on simultaneous channels |
| Inbound groups | Unlimited | ViciDial has no hard limit |
When to Split to Multiple Servers
Consider a second ViciDial server when:
- You exceed 500 concurrent agents
- Any single country has 200+ agents (give it a dedicated server)
- Network latency between the server and a country's agents exceeds 150ms
- You need SIP trunks in a specific country's data center for regulatory compliance
Quick Reference Card
File Locations
| File | Purpose |
|---|---|
/etc/asterisk/sip-vicidial.conf |
SIP peer definitions for all phones |
/etc/asterisk/ringall.conf |
Ring group dialplan extensions |
/etc/asterisk/extensions.conf |
Main Asterisk dialplan (must include ringall.conf) |
/etc/asterisk/customexte.conf |
Custom extensions (alternative to ringall.conf) |
Key Database Tables
| Table | Purpose |
|---|---|
vicidial_user_groups |
User group definitions |
vicidial_users |
Agent accounts |
phones |
Phone/extension definitions |
vicidial_campaigns |
Campaign settings |
vicidial_inbound_groups |
Inbound group settings |
vicidial_inbound_group_agents |
Agent-to-inbound-group assignments |
vicidial_inbound_dids |
DID routing rules |
Asterisk Commands
asterisk -rx "sip reload" # Reload SIP config
asterisk -rx "dialplan reload" # Reload dialplan
asterisk -rx "sip show peers" # List all SIP peers
asterisk -rx "sip show peer 2001" # Show specific peer details
asterisk -rx "dialplan show ringall_country_a@default" # Show ring group dialplan
asterisk -rx "core show channels" # Show active calls
Naming Convention Summary
| Country | Campaign | Ingroup | User Group | Phones | Ring Group |
|---|---|---|---|---|---|
| Country A | country_a |
country_a_ingr |
COUNTRY_A |
2001-2090 | ringall_country_a |
| Country B | country_b |
country_b_ingr |
COUNTRY_B |
3001-3090 | ringall_country_b |
| Country C | country_c |
country_c_ingr |
COUNTRY_C |
4001-4090 | ringall_country_c |
| (template) | {country} |
{country}_ingr |
{COUNTRY} |
X001-X090 | ringall_{country} |
Summary
Setting up isolated multi-country teams on a single ViciDial server requires coordinating changes across five layers: user groups, campaigns, inbound groups, SIP peers, and Asterisk dialplan ring groups. The individual steps are straightforward, but the number of moving parts means mistakes are common -- we found 15 issues in a single deployment, and every one of them could have caused calls to be misrouted or dropped.
The key takeaways:
- Plan your naming convention and number ranges before touching the server. Changing them later means updating every table and config file.
- Use SQL for bulk operations. The admin UI is fine for one-off changes but impractical for creating 270 phones and 90 agents.
- Always create SIP peers when you create phones. ViciDial's auto-generation does not always work for bulk operations.
- Set
phone_context=defaultlogeverywhere. Thedefaultcontext (without "log") is the most common misconfiguration. - Set
no_agent_action=EXTENSIONon every inbound group. Without a ring group fallback, after-hours calls have nowhere to go. - Test every country end-to-end. Log in as an agent, make a test call, verify it routes correctly, test the ring group fallback. Do not skip this.
- Run the cross-contamination SQL check (from Step 7) before going live. One wrong agent in one inbound group can break an entire country's call routing.
This architecture has been running in production handling calls for 3 countries on a single server. It scales cleanly to 10+ countries following the same pattern.
Based on a production deployment. All server IPs, agent names, and phone numbers in this guide are placeholders.