Voice Agent Provisioning
Summary
Provisioning spins up a new customer's phone line end-to-end: it orders a local number from Telnyx, creates a LiveKit SIP inbound trunk and dispatch rule for that number, writes a church_voice_agents row to the database, and validates the call path with a test ring. The entire flow is triggered automatically by the Stripe webhook when a customer purchases any voice plan, and falls back to a founder-email alert if automation fails. The single deployed LiveKit agent (churchwiseai-voice) is multi-tenant — it serves every church; routing happens inside session.py:resolve_route() keyed by the called phone number.
Flow
Phase tracker
- Phase 1 — Telnyx primary for new customers; legacy Twilio numbers untouched (merged 2026-03-30)
- Phase 2 — LiveKit Cloud managed agent; Stripe webhook triggers
provisionVoiceLineautomatically (merged 2026-04-04) - Phase 3 — Automated self-serve provisioning with founder-alert fallback (in progress)
- Phase 4 — Customer self-service portal: pastor chooses area code, sees number before purchase (planned)
Code files
| File | Role |
|---|---|
src/lib/voice-provision.ts | provisionVoiceLine() — full automation: Telnyx order → LiveKit trunk → dispatch rule → DB write |
src/app/api/admin/provision-number/route.ts | Manual override API: POST with token, churchId, areaCode |
src/app/api/voice/fallback/route.ts | TeXML webhook for the fallback number (+14697288326) — plays message, hangs up |
voice-agent-livekit/session.py | resolve_route() — maps called number to church UUID via PHONE_REGISTRY dict then DB fallback |
voice-agent-livekit/main.py | Agent entry point; references church_voice_agents for per-church config |
voice-agent-livekit/scripts/setup_sip.py | Manual SIP trunk + dispatch rule creation script (reference / recovery use) |
voice-agent-livekit/verticals/church/agents.py | Church-vertical agent types and coordinator logic |
voice-agent-livekit/verticals/church/church_info.py | church_voice_agents DB queries, per-church config loading |
voice-agent-livekit/core/rag.py | Knowledge-base retrieval used during live calls |
Tests
voice-live-call in knowledge/tests/registry.yaml is declared but not yet gated by CI. Before any provisioning code change, manually run the Playwright live-flow spec against the deployed preview URL and attach the artifact. "Build passes" does not substitute for a live test call.
Decisions
2026-03-25-voice-platform — LiveKit Cloud was chosen over self-hosted LiveKit and Railway to eliminate infrastructure management for a solo founder; managed scaling and built-in SIP gateway were decisive.
2026-03-30-telnyx-over-twilio — Telnyx is cheaper (~$1/mo + $0.005/min vs Twilio $1.15/mo + $0.0085/min) and connects via direct SIP to LiveKit without a TwiML bridge. Existing Twilio numbers were kept as-is; Telnyx is used for all new customer provisioning.
Gotchas
-
Main Twilio trunk
ST_Xa3Bp9aixRFPis LOCKED. Never modify its numbers, auth, or dispatch rules. An agent removed+prefixes following unrelated advice and instantly broke all working Twilio lines. Onlyvoice-agent-engineeragent type may touch LiveKit SIP infrastructure, with explicit founder approval. -
Telnyx FQDN must use project ID, not project name. Correct SIP endpoint:
5u9xu5ysoly.sip.livekit.cloud:5060(project ID). Using the name-based URLcwa-voice-9x077mph.sip.livekit.cloudreturns "404 No trunk found." The working Telnyx FQDN connection is2925216093662349036("LiveKit Inbound v2") — assign new numbers to this connection; do not create a new one. -
LiveKit SIP inbound trunks must have NO authentication set. Telnyx FQDN connections send standard SIP INVITEs without credential headers. If the LiveKit trunk has
auth_username/auth_passwordconfigured, LiveKit challenges the INVITE and rejects every call — callers hear silence or "not assigned." Security is provided by Telnyx owning the DID. -
555-prefix numbers are fiction and cannot receive calls. Pattern
^\+1\d{3}555\d{4}$is NPA-reserved for movies/TV. Some demochurch_voice_agentsrows were seeded with these as placeholders. Before telling the founder to dial any demo number, querychurch_voice_agents.twilio_phone_numberand check the pattern. Fake demos route via Demo Router at +14696152221 (US) or +13658254095 (CA) only. -
twilio_phone_numbercolumn holds Telnyx numbers too. The column name is a historical artifact. It stores whatever provider's number is assigned; the value works correctly regardless of provider. -
LiveKit CLI lives at
C:\dev\lk.exe. Deploy command:lk agent deploy --project cwa-voice --silent. Secrets are not re-uploaded on deploy — they were set via--secrets-file .envat agent creation. -
New customer numbers use Telnyx; legacy numbers stay on Twilio. Do not migrate Twilio numbers. The toll-free number (+18886030316) has ~20% connect rate on direct SIP — it uses a TwiML-to-SIP bridge via
/api/voice/twiml. -
Transport must be TCP, not UDP. LiveKit's SIP endpoint does not reliably accept UDP from Telnyx. This is set on the Telnyx FQDN connection and does not need to be changed per customer.
Current phone numbers
| Number | Church | Provider | LiveKit Trunk |
|---|---|---|---|
| +14144007103 | Medhanialem Ethiopian Evangelical Church | Telnyx | ST_LWgmiBSwTk7P |
| +17473897673 | Melvindale Church of God | Telnyx | ST_3KMJ5YEjF2fF |
| +18886030316 | Sales (toll-free) | Twilio | ST_jKtes8Md7dEZ |
| +14696152221 | Demo (US) | Twilio | ST_jKtes8Md7dEZ |
| +13658254095 | Demo (CA) | Twilio | ST_jKtes8Md7dEZ |
| +14697288326 | Company fallback (TeXML, not LiveKit) | Telnyx | N/A |
Recent activity
Reconciler (Phase 4) will populate this automatically from PR merges.