Master complex call routing, dynamic variable manipulation, and production-grade dialplan logic to build scalable ViciDial campaigns with custom extensions.conf patterns
Prerequisites
Before implementing advanced dialplan patterns in ViciDial, ensure you have:
- ViciDial installed and running (version 2.10+ recommended)
- Root or administrative access to your Asterisk server
- Working knowledge of basic Asterisk dialplan syntax (priorities, contexts, extensions)
- Familiarity with ViciDial database structure and web UI
- Understanding of SIP configuration basics
- Access to
/etc/asterisk/directory and ability to reload dialplan - Installed packages:
asterisk,asterisk-config,vicidial - Asterisk CLI access via
asterisk -rxcommands - Text editor (vim, nano, or similar) for config file editing
Understanding ViciDial Dialplan Architecture
Standard ViciDial Dialplan Structure
ViciDial uses Asterisk's dialplan system with several custom contexts. The primary configuration files are:
/etc/asterisk/extensions-vicidial.conf— Main ViciDial contexts/etc/asterisk/sip-vicidial.conf— SIP peer definitions/etc/asterisk/extensions.conf— System-wide contexts (sometimes included)
ViciDial creates channels named with the pattern SIP/xxxx-XXXXXX where xxxx is the extension and XXXXXX is the channel ID. Understanding this naming convention is critical for advanced pattern matching.
Key ViciDial Contexts to Know
[from-internal] # Agent phone extensions
[from-trunk] # Inbound calls from carriers
[from-vicidial-internal] # Internal agent calls
[default] # Fallback context
The [from-internal] context handles outbound calls from agents, while [from-trunk] processes inbound calls received from your carrier.
Section 1: Pattern Matching Fundamentals
Understanding Asterisk Pattern Matching
Asterisk pattern matching uses specific wildcard characters:
| Pattern | Meaning |
|---|---|
X |
Single digit 0-9 |
Z |
Single digit 1-9 |
N |
Single digit 2-9 |
[0-9] |
Character set (single digit) |
_PATTERN |
Literal underscore prefix (required for patterns) |
. |
Wildcard (any length) |
! |
Negation (excludes following pattern) |
ViciDial-Specific Pattern Matching
In production ViciDial systems, you'll work with several key pattern types:
; DTMF extension patterns for agent phones
_[2-9]XX ; Standard 3-digit extensions (200-999)
_[2-9]XXXX ; 4-digit extensions (2000-9999)
_91NXXNXXXXXX ; North American NANP dialing (1-800-555-0100)
_9[2-9]XXXXXXX ; Local 7-digit with trunk prefix
_9011. ; International dialing
Creating Custom Pattern Examples
[from-internal]
; Route calls prefixed with 7 to external trunk
exten => _7XXXXXXXXXXXX,1,NoOp(Long distance call from ${CALLERID(num)})
same => n,Set(MYTRUNK=SIP/carrier1)
same => n,Dial(${MYTRUNK}/${EXTEN:1},30,tT)
same => n,Hangup()
; Route calls prefixed with 6 to backup trunk
exten => _6XXXXXXXXXXXX,1,NoOp(Backup trunk routing)
same => n,Dial(SIP/carrier2/${EXTEN:1},30,tT)
same => n,Hangup()
; Internal extension routing (200-299)
exten => _2XX,1,NoOp(Routing to extension ${EXTEN})
same => n,Dial(SIP/${EXTEN},20,tT)
same => n,Voicemail(${EXTEN}@default)
same => n,Hangup()
Section 2: Variable Manipulation and Dynamic Routing
Setting and Using Channel Variables
ViciDial passes multiple variables through the call chain. The most critical are:
${DIALEDNUMBER} ; The number actually dialed
${EXTEN} ; Current extension being processed
${CALLERID(num)} ; Caller ID number
${CALLERID(name)} ; Caller ID name
${CHANNEL} ; Full channel name (SIP/200-00001234)
${CDR(billsec)} ; Billable seconds
${UNIQUEID} ; Unique call identifier
Advanced Variable Extraction
When working with long-distance numbers or special routing, extract parts of the EXTEN value:
[from-internal]
; Extract area code from NANP number prefixed with 9
exten => _91NXXNXXXXXX,1,NoOp(NANP routing - EXTEN=${EXTEN})
same => n,Set(AREACODE=${EXTEN:2:3})
same => n,Set(PREFIX=${EXTEN:5:3})
same => n,Set(LINENUMBER=${EXTEN:8:4})
same => n,Set(FULLNUMBER=${EXTEN:1})
same => n,NoOp(Extracted - Area:${AREACODE} Prefix:${PREFIX} Line:${LINENUMBER})
same => n,Dial(SIP/carrier1/${FULLNUMBER},30,tT)
same => n,Hangup()
; Strip leading 9 for PSTN calls
exten => _9.,1,NoOp(Stripping leading 9, was ${EXTEN})
same => n,Set(STRIPPED=${EXTEN:1})
same => n,Set(ORIGEXTEN=${EXTEN})
same => n,Goto(external,${STRIPPED},1)
Conditional Routing Based on Variables
[from-internal]
exten => _91NXXNXXXXXX,1,NoOp(Advanced conditional routing)
same => n,Set(CALLINGEXT=${CHANNEL(name):4:3})
same => n,GotoIf($[${CALLINGEXT} > 250]?premium:standard)
exten => _91NXXNXXXXXX,n(premium),NoOp(Premium route for ext ${CALLINGEXT})
same => n,Dial(SIP/primary-carrier/${EXTEN:1},30,tT)
same => n,Goto(next-step)
exten => _91NXXNXXXXXX,n(standard),NoOp(Standard route)
same => n,Dial(SIP/secondary-carrier/${EXTEN:1},30,tT)
exten => _91NXXNXXXXXX,n(next-step),NoOp(Call completed)
same => n,Hangup()
Section 3: ViciDial-Specific Dialplan Integration
Capturing ViciDial Campaign Variables
ViciDial populates the v_lead_id, v_campaign_id, and other variables. Use them in custom dialplan:
[from-vicidial-internal]
exten => _X.,1,NoOp(ViciDial outbound call - Campaign: ${v_campaign_id})
same => n,Set(MYVAR1=${v_lead_id})
same => n,NoOp(Lead ID: ${MYVAR1})
same => n,Dial(SIP/${EXTEN},30,tT)
same => n,Hangup()
To verify what variables ViciDial is passing, check the logs:
asterisk -rx "core set verbose 3"
tail -f /var/log/asterisk/messages | grep "NoOp"
Custom Dialplan Hooks for ViciDial Callbacks
Insert custom logic before calls are logged to vicidial_log:
[from-internal]
exten => _91NXXNXXXXXX,1,NoOp(ViciDial-tracked outbound call)
same => n,Set(CDR(userfield)=CUSTOM_TAG_${UNIQUEID})
same => n,Set(CDR(accountcode)=DEPT_${CALLERCONTEXT})
same => n,Dial(SIP/carrier/${EXTEN:1},30,tT)
same => n,NoOp(Hangup cause: ${DIALSTATUS})
same => n,Hangup()
After the call disconnects, query vicidial_log to see your custom fields:
SELECT lead_id, phone_number, call_date, length_in_sec, custom_tag
FROM vicidial_log
WHERE call_date > DATE_SUB(NOW(), INTERVAL 1 HOUR)
LIMIT 10;
Dynamic Caller ID Management
Enforce caller ID based on campaign or extension:
[from-internal]
exten => _91NXXNXXXXXX,1,NoOp(Dynamic CallerID routing)
same => n,Set(MYCALLERID=${CALLERID(num)})
same => n,GotoIf($[${MYCALLERID} = 0]?usecampaign:usedefault)
exten => _91NXXNXXXXXX,n(usecampaign),NoOp(Using campaign CallerID)
same => n,Set(CALLERID(num)=${v_campaign_cid})
same => n,Goto(docall)
exten => _91NXXNXXXXXX,n(usedefault),NoOp(Using default CallerID)
same => n,Set(CALLERID(num)=5551234567)
exten => _91NXXNXXXXXX,n(docall),Dial(SIP/carrier/${EXTEN:1},30,tT)
same => n,Hangup()
Section 4: Advanced Call Recording and Logging
Automatic Call Recording Patterns
ViciDial can record calls automatically. Use dialplan patterns to enforce recording:
[from-internal]
; Record all outbound calls to specific folder
exten => _91NXXNXXXXXX,1,NoOp(Recording outbound NANP call)
same => n,Set(FILENAME=/var/spool/asterisk/monitor/vicidial_${UNIQUEID})
same => n,MixMonitor(${FILENAME}.wav,b)
same => n,Dial(SIP/carrier/${EXTEN:1},30,tT)
same => n,StopMixMonitor()
same => n,Hangup()
; Conditional recording based on campaign
exten => _91NXXNXXXXXX,1,NoOp(Campaign: ${v_campaign_id})
same => n,GotoIf($["${v_campaign_id}" = "SALES"]?record:norecord)
exten => _91NXXNXXXXXX,n(record),MixMonitor(/var/spool/asterisk/monitor/SALES_${UNIQUEID}.wav,b)
same => n,Goto(dial)
exten => _91NXXNXXXXXX,n(norecord),NoOp(Recording disabled for this campaign)
exten => _91NXXNXXXXXX,n(dial),Dial(SIP/carrier/${EXTEN:1},30,tT)
same => n,Hangup()
Call Detail Record (CDR) Enhancement
Populate ViciDial's CDR tables with custom data:
[from-internal]
exten => _91NXXNXXXXXX,1,NoOp(Enhanced CDR logging)
same => n,Set(CDR(userfield)=ROUTE_CARRIER_001)
same => n,Set(CDR(accountcode)=ACCT_${CALLERCONTEXT})
same => n,Dial(SIP/carrier/${EXTEN:1},30,tT)
same => n,Set(FINALSTATUS=${DIALSTATUS})
same => n,NoOp(Call status: ${FINALSTATUS})
same => n,Hangup()
Check recorded CDR data:
asterisk -rx "cdr show active"
View in database:
SELECT uniqueid, src, dst, calldate, duration, billsec, userfield, accountcode
FROM cdr
WHERE dst LIKE '1%' AND calldate > DATE_SUB(NOW(), INTERVAL 1 HOUR)
ORDER BY calldate DESC;
Section 5: Failover and Redundancy Patterns
Primary and Secondary Trunk Routing
Implement automatic failover when a carrier is unavailable:
[from-internal]
exten => _91NXXNXXXXXX,1,NoOp(Dual-carrier failover routing)
same => n,Set(ATTEMPT=1)
exten => _91NXXNXXXXXX,n(retry),GotoIf($[${ATTEMPT} > 2]?fail:attempt)
exten => _91NXXNXXXXXX,n(attempt),NoOp(Attempt ${ATTEMPT})
same => n,GotoIf($[${ATTEMPT} = 1]?primary:secondary)
exten => _91NXXNXXXXXX,n(primary),NoOp(Trying primary carrier)
same => n,Dial(SIP/carrier-primary/${EXTEN:1},20,tT)
same => n,GotoIf($["${DIALSTATUS}" = "ANSWER"]?success:tryagain)
exten => _91NXXNXXXXXX,n(secondary),NoOp(Trying secondary carrier)
same => n,Dial(SIP/carrier-secondary/${EXTEN:1},20,tT)
same => n,GotoIf($["${DIALSTATUS}" = "ANSWER"]?success:fail)
exten => _91NXXNXXXXXX,n(tryagain),Set(ATTEMPT=$[${ATTEMPT} + 1])
same => n,Goto(retry)
exten => _91NXXNXXXXXX,n(success),NoOp(Call successful on ${ATTEMPT} attempt)
same => n,Hangup()
exten => _91NXXNXXXXXX,n(fail),NoOp(All carriers exhausted)
same => n,PlayBack(pbx-invalid)
same => n,Hangup()
Load Balancing Across Multiple Carriers
Distribute calls across carriers based on weight:
[from-internal]
exten => _91NXXNXXXXXX,1,NoOp(Load-balanced routing)
same => n,Set(RANDOM=${RANDOM(1,100)})
same => n,NoOp(Random value: ${RANDOM})
same => n,GotoIf($[${RANDOM} <= 40]?carrier1:carrier2check)
exten => _91NXXNXXXXXX,n(carrier1),NoOp(Routing via Carrier 1 - 40%)
same => n,Dial(SIP/carrier1/${EXTEN:1},30,tT)
same => n,Goto(end)
exten => _91NXXNXXXXXX,n(carrier2check),GotoIf($[${RANDOM} <= 80]?carrier2:carrier3)
exten => _91NXXNXXXXXX,n(carrier2),NoOp(Routing via Carrier 2 - 40%)
same => n,Dial(SIP/carrier2/${EXTEN:1},30,tT)
same => n,Goto(end)
exten => _91NXXNXXXXXX,n(carrier3),NoOp(Routing via Carrier 3 - 20%)
same => n,Dial(SIP/carrier3/${EXTEN:1},30,tT)
exten => _91NXXNXXXXXX,n(end),Hangup()
Section 6: Inbound Call Routing and DID Handling
Inbound DID Matching Patterns
ViciDial receives inbound calls in the [from-trunk] context. Map DIDs to campaigns:
[from-trunk]
; Specific 10-digit DID
exten => 5551234567,1,NoOp(Inbound DID for SALES campaign)
same => n,Set(v_campaign_id=SALES_TEAM)
same => n,Goto(distribute,1)
; DID range for support
exten => _555123[4-6]XXX,1,NoOp(Support DID range)
same => n,Set(v_campaign_id=SUPPORT_QUEUE)
same => n,Goto(distribute,1)
; Catch-all for unmapped DIDs
exten => _X.,1,NoOp(Unknown DID: ${EXTEN})
same => n,PlayBack(pbx-invalid)
same => n,Hangup()
[distribute]
exten => 1,1,NoOp(Distributing to campaign ${v_campaign_id})
same => n,Queue(${v_campaign_id},tT)
same => n,Hangup()
Inbound Call Screening with Dialplan
Implement number-blocking logic before agents answer:
[from-trunk]
exten => _X.,1,NoOp(Inbound call screening for DID ${EXTEN})
same => n,Set(CALLER=${CALLERID(num)})
same => n,Set(BLOCKED=0)
same => n,GotoIf($["${CALLER}" = "anonymous"]?blocked:checkdb)
exten => _X.,n(checkdb),NoOp(Checking blocked list)
same => n,Set(BLOCKED=${DB(blocklist/${CALLER})})
same => n,GotoIf($[${BLOCKED} = 1]?blocked:allowcall)
exten => _X.,n(blocked),NoOp(Call from ${CALLER} is blocked)
same => n,PlayBack(pbx-invalid)
same => n,Hangup()
exten => _X.,n(allowcall),NoOp(Call from ${CALLER} allowed)
same => n,Goto(distribute,1)
[distribute]
exten => 1,1,Queue(MAIN_QUEUE,tT)
same => n,Hangup()
Store blocked numbers in Asterisk's internal database:
asterisk -rx "database put blocklist 5551234567 1"
asterisk -rx "database show blocklist"
Section 7: Performance Optimization and Best Practices
Minimizing Dialplan Evaluation Time
Use GoSub() for modular, reusable code:
[from-internal]
exten => _91NXXNXXXXXX,1,NoOp(Using GoSub for routing logic)
same => n,GoSub(routing-context,validate-number,1(${EXTEN}))
same => n,Set(CARRIERID=${GOSUB_RETURN})
same => n,Dial(SIP/${CARRIERID}/${EXTEN:1},30,tT)
same => n,Hangup()
[routing-context]
exten => validate-number,1,NoOp(Received argument: ${ARG1})
same => n,GotoIf($[${LEN(${ARG1})} < 11]?invalid:valid)
exten => validate-number,n(invalid),Return(carrier-default)
exten => validate-number,n(valid),Return(carrier-premium)
Caching Variables with Dialplan Subroutines
Reduce database lookups:
[from-internal]
exten => _91NXXNXXXXXX,1,NoOp(Cache extension routing info)
same => n,Set(CACHEKEY=route_${CALLERCONTEXT})
same => n,Set(CACHED=${DB(routecache/${CACHEKEY})})
same => n,GotoIf($[! -z ${CACHED}]?usecached:lookup)
exten => _91NXXNXXXXXX,n(usecached),NoOp(Using cached value: ${CACHED})
same => n,Set(ROUTECARRIER=${CACHED})
same => n,Goto(dial)
exten => _91NXXNXXXXXX,n(lookup),NoOp(Performing fresh lookup)
same => n,Set(ROUTECARRIER=carrier1)
same => n,DB(routecache/${CACHEKEY})=${ROUTECARRIER})
exten => _91NXXNXXXXXX,n(dial),Dial(SIP/${ROUTECARRIER}/${EXTEN:1},30,tT)
same => n,Hangup()
Debug Logging for Complex Patterns
Enable verbose logging selectively:
[from-internal]
exten => _91NXXNXXXXXX,1,NoOp(=== START CALL ROUTING ===)
same => n,NoOp(Caller: ${CALLERID(num)} | Extension: ${EXTEN})
same => n,NoOp(Channel: ${CHANNEL} | UniqueID: ${UNIQUEID})
same => n,NoOp(=== END DEBUG ===)
same => n,Dial(SIP/carrier/${EXTEN:1},30,tT)
same => n,Hangup()
Enable verbose mode in Asterisk CLI:
asterisk -rx "core set verbose 3"
tail -f /var/log/asterisk/messages | grep "NoOp"
Section 8: Troubleshooting
Common Dialplan Syntax Errors
Error: Bad extension syntax
- Missing underscore before pattern: Use
_91NXXNXXXXXX, not91NXXNXXXXXX - Mismatched parentheses in GoSub/Goto statements
- Invalid characters in priority labels (use alphanumeric and hyphens only)
Error: No extension found
- Pattern is too specific and doesn't match dialed number
- Extension case-sensitivity (extensions are lowercase)
- Missing context definition for Goto/GoSub call
Debugging Call Flow
Check what extension was matched:
asterisk -rx "dialplan show from-internal | grep 91NXXNXXXXXX"
Enable debug logging for specific channels:
asterisk -rx "channel originate SIP/200 application echo"
Watch debug logs in real-time:
tail -f /var/log/asterisk/full | grep "Extension.*91"
ViciDial-Specific Issues
Issue: Variables not populating from ViciDial
Verify variables are being set by the ViciDial dialer script. Check /var/log/asterisk/messages:
grep "v_campaign_id\|v_lead_id" /var/log/asterisk/messages
If missing, verify the ViciDial script has permission to execute Asterisk commands:
ls -la /var/run/asterisk.ctl
usermod -aG asterisk root # Or appropriate user
Issue: Call recordings not appearing
Verify the monitor directory exists and has write permissions:
mkdir -p /var/spool/asterisk/monitor
chown asterisk:asterisk /var/spool/asterisk/monitor
chmod 755 /var/spool/asterisk/monitor
Check for disk space:
df -h /var/spool/asterisk/
Issue: Calls routing to wrong carrier
Add verbose logging to dialplan and compare actual values:
exten => _91NXXNXXXXXX,1,NoOp(Expected: carrier1 | Actual: ${ROUTECARRIER})
Run SQL query to verify carrier SIP peer exists:
SELECT * FROM asterisk.sip_peers WHERE name = 'carrier1';
Reload SIP configuration:
asterisk -rx "sip reload"
Testing Patterns Without Live Calls
Use the Asterisk dialplan test command:
asterisk -rx "dialplan eval _91NXXNXXXXXX"
Manually simulate extension matching:
asterisk -rx "dialplan show from-internal | grep -A5 _91"
Section 9: Advanced Production Patterns
Time-Based Routing
Route calls differently based on hour of day:
[from-internal]
exten => _91NXXNXXXXXX,1,NoOp(Time-based routing)
same => n,Set(HOUR=${STRFTIME(${EPOCH},%-H)})
same => n,GotoIf($[${HOUR} >= 9 && ${HOUR} < 17]?businesshours:afterhours)
exten => _91NXXNXXXXXX,n(businesshours),NoOp(Business hours routing)
same => n,Dial(SIP/primary-carrier/${EXTEN:1},30,tT)
same => n,Goto(end)
exten => _91NXXNXXXXXX,n(afterhours),NoOp(After-hours routing)
same => n,Dial(SIP/backup-carrier/${EXTEN:1},30,tT)
exten => _91NXXNXXXXXX,n(end),Hangup()
Geographic Routing
Route based on area code using a database:
[from-internal]
exten => _91NXXNXXXXXX,1,NoOp(Geographic routing based on area code)
same => n,Set(AREACODE=${EXTEN:2:3})
same => n,Set(REGION=${DB(regions/${AREACODE})})
same => n,GotoIf($[! -z ${REGION}]?routeregion:routedefault)
exten => _91NXXNXXXXXX,n(routeregion),NoOp(Routing to ${REGION} carrier)
same => n,Set(CARRIER=${DB(carriers/${REGION})})
same => n,Dial(SIP/${CARRIER}/${EXTEN:1},30,tT)
same => n,Goto(end)
exten => _91NXXNXXXXXX,n(routedefault),Dial(SIP/default-carrier/${EXTEN:1},30,tT)
exten => _91NXXNXXXXXX,n(end),Hangup()
Populate the database:
asterisk -rx "database put regions 415 california"
asterisk -rx "database put regions 212 newyork"
asterisk -rx "database put carriers california carrier-west"
asterisk -rx "database put carriers newyork carrier-east"
Summary
Advanced ViciDial custom dialplan patterns enable sophisticated call routing, failover logic, and integration with external systems. Key takeaways:
- Pattern matching forms the foundation—master
_X,Z,N, and.wildcards - Variable manipulation with
${EXTEN:offset:length}extracts specific call components - Conditional logic (GotoIf, GoSub) creates dynamic routing rules
- ViciDial integration leverages campaign, lead, and caller ID variables for contextual routing
- Recording and logging with MixMonitor and CDR enhance compliance and analytics
- Failover patterns ensure reliability across multiple carriers
- Caching and modular code optimize performance in high-volume environments
- Verbose logging and systematic testing prevent production issues
Reload your dialplan safely:
asterisk -rx "dialplan reload"
Monitor changes:
tail -f /var/log/asterisk/full
Test thoroughly in a staging environment before deploying to production. Advanced dialplan patterns are powerful but require careful testing to avoid dropped calls and routing errors.