LiveKit BYO SIP Trunk on SignalWire
LiveKit's Agents framework runs the AI conversation; SignalWire handles PSTN bridging. Same shape as the VAPI BYO setup — the differences are transport (LiveKit inbound needs ;transport=tcp, outbound trunk needs TLS) and the destination SIP domain.
LiveKit Inbound — PSTN → LiveKit
Flow:
- Caller dials a SignalWire DID.
- SignalWire executes the SWML.
connectbridges to LiveKit's SIP domain.- LiveKit's agent pipeline handles the call.
SWML
version: 1.0.0
sections:
main:
- connect:
to: sip:%{call.to}@your-unique-SIP-domain-from-Livekit.com;transport=tcp
;transport=tcp is required for LiveKit inbound. Without it, calls may fail or behave unpredictably.
Optional — record the call
version: 1.0.0
sections:
main:
- record_call:
stereo: true
format: mp3
- connect:
to: sip:%{call.to}@your-unique-SIP-domain-from-Livekit.com;transport=tcp
Recordings appear in the SignalWire Dashboard Logs tab.
Setup steps
1. Create the SWML in SignalWire with your LiveKit SIP domain substituted in.
2. Assign the SWML to the DID that receives inbound calls.
3. Configure LiveKit's inbound SIP trunk:
- Create an inbound SIP trunk in LiveKit.
- Add your SignalWire phone number(s) in E.164 format.
- Save.
LiveKit Outbound — LiveKit → PSTN
Flow:
- LiveKit initiates a SIP call to a SignalWire SIP Address.
- SignalWire executes the SWML at that SIP Address.
- SWML
connectbridges to PSTN. - Callee sees the call from your SignalWire DID.
SWML
version: 1.0.0
sections:
main:
- connect:
answer_on_bridge: true
from: "+1XXXXXXXXXX"
to: "%{call.to.replace(/^sip:/i, '').replace(/@.*/, '')}"
The to expression strips sip: prefix and @domain suffix. answer_on_bridge: true is required.
Optional — outbound with recording (sub-SWML via confirm)
# record-helper.yaml
version: 1.0.0
sections:
main:
- record_call: { stereo: true, format: mp3 }
# main.yaml (assigned to the SIP Address)
version: 1.0.0
sections:
main:
- connect:
answer_on_bridge: true
from: "+1XXXXXXXXXX"
to: "%{call.to.replace(/^sip:/i, '').replace(/@.*/, '')}"
confirm: https://your.api/record-helper.yaml
Setup steps
1. Create the SWML in SignalWire with your outbound caller ID.
2. Create a SIP Address in SignalWire:
- SIP → SIP Addresses → New.
- Assign the SWML script.
- Note the SIP domain (e.g.,
your-space.sip.signalwire.com).
3. Request the app password from SignalWire Support. Not available in the portal UI. Required for LiveKit's outbound trunk auth.
4. Create the LiveKit outbound SIP trunk:
| Field | Value |
|---|---|
| Address | Your SignalWire SIP domain |
| Numbers | Your SignalWire DID(s) |
| Auth Username | Your SignalWire phone number (E.164) |
| Auth Password | App password from Support |
| Transport | 3 (TLS — required) |
5. Initiate calls using LiveKit's API — create a SIP Participant, which triggers an outbound call through the trunk.
SignalWire IPs
When registering SignalWire as a BYO inbound trunk in LiveKit, all SignalWire SIP IPs should be allowed:
dig sip.signalwire.com
LiveKit's inbound trunk lets you list multiple addresses. Re-check periodically.
Troubleshooting
LiveKit inbound calls failing
- [ ] LiveKit SIP domain is correct in the SWML.
- [ ]
;transport=tcpappended to the SIP URI. - [ ] SWML assigned to the SignalWire DID (not a SIP Address).
- [ ] SignalWire IPs allowed in LiveKit inbound trunk.
- [ ] SignalWire Logs tab shows SWML execution.
LiveKit outbound auth errors
- [ ] App password obtained from SignalWire Support.
- [ ] Auth username is the E.164 phone number, not a string.
- [ ] SIP domain matches the SignalWire SIP Address exactly.
- [ ] Transport set to TLS (
3). - [ ] SWML assigned to the SIP Address (not a phone number).
- [ ]
answer_on_bridge: truein the SWML.
Anti-patterns
- Omitting
;transport=tcpon the LiveKit inbound SIP URI — silently fails. - Skipping TLS (transport
3) on the LiveKit outbound trunk — auth fails. - Using an alphanumeric auth username instead of the E.164 number.
- Generating the app password locally — must come from SignalWire Support.
- Assigning outbound SWML to a phone number — must be on a SIP Address.