Voice URL Setup
For Loquent to handle inbound calls on a phone number, that number’s Voice URL in Twilio must point to Loquent’s webhook endpoint. This page covers how voice URLs work, how they’re configured for different account types, and the APIs behind per-phone configuration.
What the Voice URL Is
Section titled “What the Voice URL Is”When an inbound call arrives on a Twilio number, Twilio makes an HTTP POST to the number’s configured Voice URL and executes whatever TwiML it receives back.
Loquent’s voice endpoint is:
POST https://{APP_HOST}/twilio/voiceWhen Twilio hits this endpoint, Loquent:
- Starts recording the call (both tracks, dual channels)
- Sets up a recording status callback to
/twilio/recording - Opens a WebSocket media stream to
/twilio/stream - Passes
fromandtoparameters to the stream for AI agent routing
When the Voice URL Is Set
Section titled “When the Voice URL Is Set”| Scenario | Behavior |
|---|---|
| Buy a number through Loquent | Voice URL set automatically at purchase |
| Import a number (non-BYO) | Must configure manually via Twilio Console |
| Import a number (BYO account) | Configure per-phone through the Loquent UI |
BYO Per-Phone Voice Configuration
Section titled “BYO Per-Phone Voice Configuration”BYO (Bring Your Own) Twilio organizations can configure voice webhooks on each imported number directly from Settings → Twilio Connection → Configure Voice Webhooks.
Each number shows a Configure Voice button. When you click it:
- Loquent fetches the number’s current voice URL from Twilio
- Saves it to
phone_number.previous_voice_urlin the database - Sets the number’s voice URL to
https://{APP_HOST}/twilio/voice - The number now routes inbound calls through Loquent
To undo this, click Restore on a configured number. Loquent writes the saved URL back to Twilio and clears the stored value.
Configure Voice API
Section titled “Configure Voice API”POST /api/twilio/configure-phone-voiceContent-Type: application/json
{ "phone_number_id": "d4f8a2c1-9b3e-4a7f-b5d6-1e8c3f2a9b4d"}Response:
{ "previous_voice_url": "https://old-platform.com/voice"}The previous_voice_url is null when the number had no voice URL set in Twilio. Loquent stores an empty string internally to distinguish “no URL” from “not yet configured.”
Error responses:
| Status | Reason |
|---|---|
| 400 | Not a BYO account, or number already configured |
| 403 | Missing TwilioInstanceAction::Update permission |
| 404 | Phone number not found |
| 502 | Twilio API call failed |
Restore Voice API
Section titled “Restore Voice API”POST /api/twilio/restore-phone-voiceContent-Type: application/json
{ "phone_number_id": "d4f8a2c1-9b3e-4a7f-b5d6-1e8c3f2a9b4d"}Returns 204 No Content on success. Writes the stored previous_voice_url back to Twilio and sets the DB field to NULL.
State Tracking
Section titled “State Tracking”The previous_voice_url column on phone_number drives all state:
| Value | Meaning |
|---|---|
NULL | Not configured by Loquent — shows “Configure Voice” button |
"" (empty) | Configured; Twilio had no previous URL |
"https://..." | Configured; original URL saved for restore |
The GET /api/twilio/imported-numbers endpoint returns this field on each PhoneNumberSummary:
[ { "id": "d4f8a2c1-9b3e-4a7f-b5d6-1e8c3f2a9b4d", "number": "+15551234567", "friendly_name": "Support Line", "previous_voice_url": null }]How the Voice Webhook Works
Section titled “How the Voice Webhook Works”Twilio → POST /twilio/voice Params: From=+15551234567, To=+15559876543
Loquent handler responds with TwiML: <Response> <Start> <Stream url="wss://{APP_HOST}/twilio/stream"> <Parameter name="from" value="+15551234567"/> <Parameter name="to" value="+15559876543"/> </Stream> </Start> <Record recordingStatusCallback="/twilio/recording" recordingChannels="dual" .../> </Response>The media stream WebSocket (/twilio/stream) receives audio in real time and routes it to the assigned AI agent on that phone number.
All Webhook Paths
Section titled “All Webhook Paths”| Path | Method | Purpose |
|---|---|---|
/twilio/voice | POST | Inbound call entry — returns TwiML |
/twilio/stream | WebSocket | Real-time audio media stream |
/twilio/recording | POST | Recording completion callback |
/twilio/events | POST | SMS event sink (see SMS Sync) |
All webhook paths are public (no session auth) because Twilio calls them directly.
Module Structure
Section titled “Module Structure”src/mods/twilio/├── api/│ ├── twilio_voice_api.rs # POST /twilio/voice│ ├── twilio_recording_api.rs # POST /twilio/recording│ ├── configure_phone_voice_api.rs # POST /api/twilio/configure-phone-voice│ ├── restore_phone_voice_api.rs # POST /api/twilio/restore-phone-voice│ ├── get_imported_phone_numbers_api.rs # GET /api/twilio/imported-numbers│ └── routes/│ └── twilio_stream_route.rs # WebSocket /twilio/stream├── types/│ └── configure_phone_voice_response_type.rs # ConfigurePhoneVoiceResponse└── utils/ ├── get_twilio_phone_number_voice_url_util.rs # Fetches current VoiceUrl from Twilio └── set_twilio_phone_number_twiml_util.rs # Sets VoiceUrl via Twilio API
src/mods/settings/components/├── configure_voice_section_component.rs # Per-phone configure/restore UI└── settings_collapsible_section_component.rs # Reusable collapsible wrapperMigration
Section titled “Migration”m20260301_000000_phone_number_add_previous_voice_url adds the nullable previous_voice_url text column to phone_number. Rollback drops it.