Periodic Trunk Sync
Overview
The trunk sync service runs a bidirectional sync between MongoDB and LiveKit every minute via a cron job. MongoDB is the single source of truth. The periodic sync ensures LiveKit state always matches the database, replacing the previous reactive (event-driven) approach.
Sync Schedule
| Trigger | Frequency | Scope |
|---|---|---|
| Server startup | Once | All trunks |
| Cron job | Every 1 minute | All trunks |
| Manual API call | On demand | All trunks |
Manual Trigger
POST /telephony/superadmin/sip-trunks/syncRequires superadmin role. Calls the same syncAllTrunks() function.
Sync Behavior
Each sync run performs two phases sequentially:
Phase 1: Outbound Trunks
- Fetch all active outbound trunks from MongoDB
- Fetch all outbound trunks from LiveKit
- For each DB trunk:
- If it exists on LiveKit → check for config drift (name, address, numbers). If drifted → update LiveKit to match DB
- If it's missing on LiveKit → recreate it and store the new
livekitTrunkIdin MongoDB
- For each LiveKit trunk not referenced by any DB document → delete it (orphan cleanup)
- When a trunk is recreated with a new LiveKit ID → update Redis campaign configs for any running campaigns using that trunk
Phase 2: Inbound Trunks
- Fetch all active inbound trunks from MongoDB
- Fetch all inbound trunks and dispatch rules from LiveKit
- For each DB trunk:
- If it exists on LiveKit → check for config drift. If drifted → update LiveKit
- If its dispatch rule is missing → recreate the dispatch rule
- If it's missing on LiveKit → recreate trunk + dispatch rule, update MongoDB
- Orphan cleanup: delete any LiveKit inbound trunks or dispatch rules not referenced by DB
Redis Campaign Config Update
When an outbound trunk is recreated (new LiveKit ID), the sync also updates Redis for running campaigns:
- Finds all campaigns with
isRunning: true - For each, checks
campaign:{uid}:config→trunkUIDfield - If it matches the recreated trunk's
trunkUID→ updatessipTrunkIdwith the new LiveKit ID
This ensures active campaigns pick up the new trunk ID on their next call dispatch without requiring a campaign restart.
Overlap Protection
A simple in-memory lock (isSyncing flag) prevents concurrent sync executions. If the cron fires while a previous sync is still in progress, the new invocation returns immediately with a "skipped" status.
Config Drift Detection
Outbound Trunks
A drift is detected if any of these differ between DB and LiveKit:
- Trunk name
- SIP address
- Phone numbers (order-independent comparison)
Inbound Trunks
A drift is detected if any of these differ:
- Trunk name
- Phone numbers (order-independent comparison)
Agent Behavior on "Trunk Not Found"
When the Python agent encounters a "trunk does not exist" error from LiveKit during a call attempt:
- The agent logs the error
- Sets disconnect reason to
trunk_not_found - Deletes the room (fails gracefully)
- Does not retry or call any sync endpoint
The periodic cron (within the next 60 seconds) will recreate the missing trunk on LiveKit. The campaign retry mechanism will then re-attempt the contact on a subsequent cycle.
Logging
| Condition | Log Output |
|---|---|
| No changes detected | 🔄 Trunk sync (cron): all in sync (outbound: N, inbound: M) |
| Changes detected | 🔄 Trunk sync (cron): changes detected + full report |
| Sync skipped (overlap) | No log (silent skip) |
| Sync error | ❌ Trunk sync cron error: ... |
Files
| File | Role |
|---|---|
services/trunkSync.service.ts | Core sync logic (syncAll, outbound/inbound phases, Redis update) |
services/scheduler.service.ts | Cron registration (every minute) |
services/livekit.service.ts | LiveKit SDK wrappers (create, update, delete, list) |