chan_sip has been deprecated for some time, and carriers increasingly test only against PJSIP. VICIdial’s own configuration engine still writes chan_sip peers, which leaves you needing both stacks on one box.
This is doable and stable. It just needs the two channel drivers kept out of each other’s way.
Understanding the Split
VICIdial generates sip-vicidial.conf from the database — agent phones, conferences, and any carrier you define through the admin interface. That is chan_sip and it will remain chan_sip.
PJSIP is configured the traditional way, in /etc/asterisk/pjsip.conf, by hand. VICIdial does not manage it. You then route calls to the PJSIP trunk from a carrier’s Dialplan Entry field.
The practical implication: your carrier entry in VICIdial will have an empty Account Entry and Registration String. All the trunk configuration lives in pjsip.conf. Only the Dialplan Entry is used.
1. Port Separation
Both drivers cannot bind UDP 5060. Decide which gets it.
Keep chan_sip on 5060 for agent phones — it is the path of least resistance since VICIdial writes those entries. Give PJSIP 5070.
In /etc/asterisk/sip.conf, under [general]:
bindport=5060
bindaddr=0.0.0.0
In /etc/asterisk/pjsip.conf:
[transport-udp]
type=transport
protocol=udp
bind=0.0.0.0:5070
Behind NAT, add:
local_net=192.168.1.0/24
external_media_address=203.0.113.10
external_signaling_address=203.0.113.10
Tell your carrier you are signalling from port 5070. Most do not care, but IP-authenticated providers sometimes pin the source port.
2. Load Order
In /etc/asterisk/modules.conf, make sure both load and neither is preloaded ahead of the other:
[modules]
autoload=yes
noload => chan_sip.so ; do NOT set this — VICIdial needs chan_sip
That line is shown as a warning, not an instruction. If you noload chan_sip, every agent phone stops registering. Leave autoload alone unless you have a specific reason.
Verify both are present:
asterisk -rx "module show like chan_sip"
asterisk -rx "module show like chan_pjsip"
3. Registration-Based PJSIP Trunk
Complete working block for a provider requiring username/password registration:
[myprovider]
type=registration
transport=transport-udp
outbound_auth=myprovider-auth
server_uri=sip:sip.provider.com
client_uri=sip:15551234567@sip.provider.com
contact_user=15551234567
retry_interval=60
forbidden_retry_interval=600
expiration=3600
line=yes
endpoint=myprovider
[myprovider-auth]
type=auth
auth_type=userpass
username=15551234567
password=YourSecretHere
[myprovider]
type=aor
contact=sip:sip.provider.com:5060
qualify_frequency=60
[myprovider]
type=endpoint
transport=transport-udp
context=trunkinbound
disallow=all
allow=ulaw
allow=alaw
outbound_auth=myprovider-auth
aors=myprovider
from_user=15551234567
from_domain=sip.provider.com
direct_media=no
rtp_symmetric=yes
force_rport=yes
rewrite_contact=yes
dtmf_mode=rfc4733
trust_id_inbound=no
send_rpid=yes
[myprovider]
type=identify
endpoint=myprovider
match=sip.provider.com
Points that matter:
context=trunkinbound— same as chan_sip. Inbound calls must land in VICIdial’s inbound context or DID routing will not fire.direct_media=no— non-negotiable if you record calls.line=yeswithendpoint=on the registration block associates inbound traffic from the registration with the right endpoint. Without it, inbound calls can arrive unmatched.type=identifymatches inbound INVITEs by source. If your carrier sends from a different IP than the proxy you register to, list all of them:
[myprovider]
type=identify
endpoint=myprovider
match=203.0.113.45
match=203.0.113.46
4. IP-Authenticated Trunk
Simpler — no registration or auth sections:
[trunkB]
type=endpoint
transport=transport-udp
context=trunkinbound
disallow=all
allow=ulaw
aors=trunkB
direct_media=no
rtp_symmetric=yes
force_rport=yes
dtmf_mode=rfc4733
[trunkB]
type=aor
contact=sip:198.51.100.20:5060
qualify_frequency=60
[trunkB]
type=identify
endpoint=trunkB
match=198.51.100.20
5. Reload and Verify
asterisk -rx "pjsip reload"
asterisk -rx "pjsip show registrations"
asterisk -rx "pjsip show endpoints"
asterisk -rx "pjsip show aors"
For registration you want Registered. For the AOR, Avail with a round-trip time.
If registration fails:
asterisk -rvvvvv
pjsip set logger on
Read the 401/403 response. 403 Forbidden on an IP trunk means you are not yet whitelisted. 401 repeating means credentials.
Turn the logger off afterwards.
6. Routing VICIdial Calls Over PJSIP
Now create the carrier in VICIdial. Leave Registration String and Account Entry empty. In Dialplan Entry:
exten => _91NXXNXXXXXX,1,AGI(agi://127.0.0.1:4577/call_log)
exten => _91NXXNXXXXXX,n,Dial(PJSIP/${EXTEN:1}@myprovider,,tTo)
exten => _91NXXNXXXXXX,n,Hangup()
Note the syntax difference. chan_sip is SIP/peer/number; PJSIP is PJSIP/number@endpoint. Getting this backwards produces “endpoint not found” errors that look like a configuration problem but are not.
Setting caller ID on PJSIP:
exten => _9X.,1,AGI(agi://127.0.0.1:4577/call_log)
exten => _9X.,n,Set(CALLERID(num)=15551230000)
exten => _9X.,n,Dial(PJSIP/${EXTEN:1}@myprovider,,tTo)
exten => _9X.,n,Hangup()
If your carrier reads caller ID from the P-Asserted-Identity header rather than From, keep send_rpid=yes on the endpoint, or use:
send_pai=yes
7. Inbound
Inbound needs nothing special beyond context=trunkinbound. The existing VICIdial inbound dialplan handles it:
[trunkinbound]
exten => _X.,1,AGI(agi-DID_route.agi)
exten => _X.,n,Hangup()
Then add the DID under Admin → Inbound → Show DIDs and point it at your in-group.
If the carrier sends the DID with a leading +:
exten => _+X.,1,Goto(trunkinbound,${EXTEN:1},1)
8. Firewall
firewall-cmd --permanent --add-port=5070/udp
firewall-cmd --permanent --add-port=10000-20000/udp
firewall-cmd --reload
Scope to carrier source IPs rather than opening these globally.
Troubleshooting Notes
Summary
Keep chan_sip on 5060 for VICIdial’s own phone management, put PJSIP on 5070 for carrier trunks, configure the trunk entirely in pjsip.conf, and use the carrier’s Dialplan Entry only. The two stacks coexist without conflict as long as ports and contexts are kept distinct.