Skip to content

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.

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/voice

When Twilio hits this endpoint, Loquent:

  1. Starts recording the call (both tracks, dual channels)
  2. Sets up a recording status callback to /twilio/recording
  3. Opens a WebSocket media stream to /twilio/stream
  4. Passes from and to parameters to the stream for AI agent routing
ScenarioBehavior
Buy a number through LoquentVoice 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 (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:

  1. Loquent fetches the number’s current voice URL from Twilio
  2. Saves it to phone_number.previous_voice_url in the database
  3. Sets the number’s voice URL to https://{APP_HOST}/twilio/voice
  4. 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.

POST /api/twilio/configure-phone-voice
Content-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:

StatusReason
400Not a BYO account, or number already configured
403Missing TwilioInstanceAction::Update permission
404Phone number not found
502Twilio API call failed
POST /api/twilio/restore-phone-voice
Content-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.

The previous_voice_url column on phone_number drives all state:

ValueMeaning
NULLNot 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
}
]
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.

PathMethodPurpose
/twilio/voicePOSTInbound call entry — returns TwiML
/twilio/streamWebSocketReal-time audio media stream
/twilio/recordingPOSTRecording completion callback
/twilio/eventsPOSTSMS event sink (see SMS Sync)

All webhook paths are public (no session auth) because Twilio calls them directly.

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 wrapper

m20260301_000000_phone_number_add_previous_voice_url adds the nullable previous_voice_url text column to phone_number. Rollback drops it.