← All Tutorials

Setting Up Isolated Multi-Country Teams on a Single ViciDial Server

ViciDial Administration Intermediate 38 min read #12

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

  1. Introduction
  2. Architecture Overview
  3. Planning: Naming Conventions and Number Ranges
  4. Step 1 -- Create User Groups
  5. Step 2 -- Create Campaigns
  6. Step 3 -- Create Inbound Groups
  7. Step 4 -- Create Phones (Bulk SQL)
  8. Step 5 -- Create SIP Peers in Asterisk
  9. Step 6 -- Create Agent User Accounts (Bulk SQL)
  10. Step 7 -- Assign Agents to Inbound Groups
  11. Step 8 -- Configure Ring Group Fallback Extensions
  12. Step 9 -- Route DIDs to Inbound Groups
  13. Step 10 -- Reload and Verify
  14. Common Pitfalls: 15 Issues We Found in Production
  15. Testing Checklist
  16. Monitoring Per-Country Performance
  17. Scaling to More Countries
  18. 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:

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


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:

  1. User Group -- Controls which campaigns and inbound groups an agent can see in the admin UI and agent interface.
  2. Campaign allowed_campaigns -- Restricts which user groups can log into a campaign.
  3. Inbound Group agent list -- Only agents explicitly added to an inbound group receive its calls.
  4. Phone user_group -- Ties each SIP phone to a country, so phones appear in the correct admin filter.
  5. 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:

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?

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

  1. Navigate to Admin > User Groups > Add a New User Group
  2. 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
  1. Click Submit
  2. Repeat for COUNTRY_B and COUNTRY_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_campaigns field 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

  1. Navigate to Admin > Campaigns > Add a New Campaign
  2. 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
  1. Under Detail View > Allowed Inbound Groups, add country_a_ingr
  2. 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

  1. Navigate to Admin > Inbound Groups > Add a New Group
  2. 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
  1. 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
  1. 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.IP with 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 = 1 for 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

  1. Go to Admin > Inbound Groups > click on country_a_ingr
  2. Click the Agents link (or scroll to the agent assignment section)
  3. Add each Country A agent (2001-2030) to the group
  4. Set their rank (1-9, where 9 is highest priority)
  5. Repeat for country_b_ingr (agents 3001-3030) and country_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

  1. A call arrives for Country A
  2. ViciDial's inbound group country_a_ingr looks for an available agent
  3. No agent is available (no_agent_no_queue=NO_READY)
  4. ViciDial executes no_agent_action=EXTENSION with value ringall_country_a,default
  5. The call hits the Asterisk dialplan extension ringall_country_a in context [default]
  6. The extension rings 40 phones simultaneously, retrying 25 times with 4-second timeout
  7. 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?

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

  1. Go to Admin > Inbound DIDs
  2. Click Add a New DID
  3. Configure:
Field Value
DID Extension The full phone number (e.g., 441234567890)
DID Route IN_GROUP
Group ID country_a_ingr
Active Y
  1. 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:

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:


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:

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:

  1. Choose a number range: 5001-5090 (next available block)
  2. Create user group: COUNTRY_D via SQL
  3. Create campaign: country_d with campaign_allow_inbound=Y
  4. Create inbound group: country_d_ingr with no_agent_action=EXTENSION, value ringall_country_d,default
  5. Create 90 phones: SQL INSERT with user_group=COUNTRY_D, phone_context=defaultlog
  6. Create 90 SIP peers: Append to sip-vicidial.conf, asterisk -rx "sip reload"
  7. Create agent accounts: SQL INSERT with user_group=COUNTRY_D, agent_choose_ingroups=1
  8. Assign agents to inbound group: SQL INSERT into vicidial_inbound_group_agents
  9. Create ring group: Append to ringall.conf, asterisk -rx "dialplan reload"
  10. Route DIDs: Point Country D DIDs to country_d_ingr
  11. 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:


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:

  1. Plan your naming convention and number ranges before touching the server. Changing them later means updating every table and config file.
  2. Use SQL for bulk operations. The admin UI is fine for one-off changes but impractical for creating 270 phones and 90 agents.
  3. Always create SIP peers when you create phones. ViciDial's auto-generation does not always work for bulk operations.
  4. Set phone_context=defaultlog everywhere. The default context (without "log") is the most common misconfiguration.
  5. Set no_agent_action=EXTENSION on every inbound group. Without a ring group fallback, after-hours calls have nowhere to go.
  6. 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.
  7. 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.

Need expert help with your setup?

VoIP infrastructure consulting, AI voice agent integration, monitoring stacks, scaling — I've done it all in production.

Get a Free Consultation