SIP Trunk System
Architecture Overview
MongoDB is the single source of truth for all SIP trunk configuration. LiveKit SIP trunks are created via the LiveKit SDK and their IDs are stored back in MongoDB. A periodic sync service (every minute) ensures LiveKit state always matches the database recreating missing trunks, correcting config drift, and cleaning up orphans.
SIPTrunk Model Schema
{
type: "outbound" | "inbound",
livekitTrunkId: string, // LiveKit outbound trunk ID
livekitInboundTrunkId: string, // LiveKit inbound trunk ID
dispatchRuleId: string, // LiveKit dispatch rule ID (inbound only)
trunkConfig: {
provider: string,
numbers: string[],
address: string,
username: string,
password: string,
// Additional provider-specific fields
},
status: "active" | "inactive" | "error"
}Outbound Trunk Creation Flow
- User submits trunk configuration (provider, numbers, SIP credentials)
- Backend calls LiveKit SDK
createSipOutboundTrunk()with the trunk config - LiveKit returns the created trunk with an ID
- Backend stores the
livekitTrunkIdin the MongoDB SIPTrunk document - Phone numbers from the trunk config are auto-registered in the PhoneNumber pool
Inbound Trunk Creation Flow
- User submits inbound trunk configuration
- Backend calls LiveKit SDK
createSipInboundTrunk()with numbers and SIP credentials - LiveKit returns the inbound trunk with an ID → stored as
livekitInboundTrunkId - Backend immediately creates a dispatch rule linked to the inbound trunk (1:1 relationship)
- LiveKit returns the dispatch rule ID → stored as
dispatchRuleId - Phone numbers from the trunk config are auto-registered in the PhoneNumber pool
Dispatch Rule Configuration
The dispatch rule is created with minimal metadata:
{
"mode": "inbound",
"need_to_update_agent": true
}No agent identity is stored in dispatch metadata. Agent resolution happens at call time.
Sync Service
Periodic Sync (Every Minute)
A cron job runs syncAllTrunks() every 60 seconds. It performs a full bidirectional sync:
- Outbound phase: Recreates missing trunks on LiveKit, updates drifted config, deletes orphans, and updates Redis campaign configs when trunks are recreated
- Inbound phase: Same logic plus dispatch rule reconciliation (recreates missing rules, deletes orphaned ones)
An overlap lock prevents concurrent sync runs if a previous sync is still in progress.
See PERIODIC-SYNC.md for full details on sync behavior, drift detection, and Redis updates.
Startup Sync
On application boot, the same syncAllTrunks() runs once before accepting traffic. This is identical to the periodic sync.
Manual Sync
Superadmins can trigger a sync on demand via:
POST /telephony/superadmin/sip-trunks/syncTrunk Deletion
When a trunk is deleted:
- LiveKit resources are removed (trunk + dispatch rule for inbound)
- The MongoDB SIPTrunk document is deleted
- Number cascade: associated phone numbers are cleaned up (removed from pool or unlinked)
LiveKit SDK Interactions
| Operation | SDK Method |
|---|---|
| Create outbound trunk | createSipOutboundTrunk() |
| Create inbound trunk | createSipInboundTrunk() |
| Create dispatch rule | createSipDispatchRule() |
| Delete outbound trunk | deleteSipTrunk() |
| Delete inbound trunk | deleteSipTrunk() |
| Delete dispatch rule | deleteSipDispatchRule() |
| List trunks (sync) | listSipTrunk() |
Configuration
| Environment Variable | Purpose |
|---|---|
LIVEKIT_URL | LiveKit server WebSocket URL |
LIVEKIT_API_KEY | API key for LiveKit SDK authentication |
LIVEKIT_API_SECRET | API secret for LiveKit SDK authentication |
AGENT_NAME | Name of the Python agent for dispatch |
Multi-Provider Support
Different SIP providers (Telnyx, Twilio, etc.) work independently. Each trunk stores its own provider-specific configuration in trunkConfig. The system does not enforce a single provider multiple providers can coexist with their own trunks and numbers.
Key Constraints
| Constraint | Enforcement |
|---|---|
| One number → one inbound trunk | Enforced (unique constraint) |
| One number → multiple outbound trunks | Allowed |
| One inbound trunk → one dispatch rule | 1:1 relationship, always created together |