Appearance
Telephony Providers
The platform supports three telephony providers through a common abstraction layer. Each provider implements the same interface: transport creation, webhook response building, and outbound call initiation.
Provider Comparison
| Feature | Twilio | Exotel | Vobiz |
|---|---|---|---|
| Audio encoding | mu-law 8 kHz | PCM 8 kHz | mu-law 8 kHz |
| Serializer | TwilioFrameSerializer | ExotelFrameSerializer | VobizFrameSerializer |
| Inbound webhook format | TwiML | ExoML | Vobiz XML |
| Outbound API | calls.create() | /Calls/connect.json | REST API |
| Auto hang-up | Yes (via REST) | No | Yes (via serializer) |
| DTMF support | Yes | Yes | Yes |
| Default concurrency | 10 | 10 | 10 |
Switching Providers
Set the default provider in .env:
env
TELEPHONY_PROVIDER=twilio # or exotel, or vobizOr configure via the settings API:
bash
curl -X PUT http://localhost:8000/api/settings/telephony \
-H "Content-Type: application/json" \
-d '{ "provider": "exotel" }'Individual outbound calls can override the provider per-call:
json
{ "provider": "exotel" }Twilio
Required Settings
| Variable | Description |
|---|---|
TWILIO_ACCOUNT_SID | Account SID from Twilio console |
TWILIO_AUTH_TOKEN | Auth token from Twilio console |
PUBLIC_BASE_URL | Public HTTPS URL for webhooks |
Optional:
| Variable | Description |
|---|---|
TWILIO_PHONE_NUMBER | Default caller ID for outbound calls |
Inbound Setup
Configure your Twilio phone number's Voice webhook:
POST https://your-domain.com/telephony/twilio/inbound/{agent_name}The app returns TwiML that connects Twilio to the WebSocket:
xml
<Response>
<Connect>
<Stream url="wss://your-domain.com/ws/twilio/{agent_name}">
<Parameter name="agent_name" value="sales-agent" />
<Parameter name="direction" value="inbound" />
<Parameter name="from_number" value="+1..." />
<Parameter name="to_number" value="+1..." />
<Parameter name="twilio_call_sid" value="CA..." />
</Stream>
</Connect>
</Response>Outbound Flow
POST /api/calls/outboundcreates a call record- Twilio REST API
calls.create()is called with inline TwiML - TwiML connects the answered call to
wss://.../ws/twilio/{agent_name} - WebSocket handler starts the voice pipeline
Testing Checklist
- Backend running and accessible
- Public tunnel active (ngrok/cloudflared for local dev)
PUBLIC_BASE_URLmatches the tunnel URL- Twilio webhook pointed at
/telephony/twilio/inbound/{agent_name} - Agent exists in the database
- STT/LLM/TTS API keys configured
Exotel
Required Settings
| Variable | Description |
|---|---|
EXOTEL_ACCOUNT_SID | Account SID from Exotel dashboard |
EXOTEL_API_KEY | API key |
EXOTEL_API_TOKEN | API token |
PUBLIC_BASE_URL | Public HTTPS URL for webhooks |
Optional:
| Variable | Description |
|---|---|
EXOTEL_PHONE_NUMBER | Default virtual number |
EXOTEL_API_BASE | API base URL (defaults to Exotel's standard endpoint) |
Inbound Setup
- Log in to Exotel App Bazaar
- Create a Voicebot applet
- Set the webhook URL to:
POST https://your-domain.com/telephony/exotel/inbound/{agent_name} - Assign the applet to your virtual number's inbound flow
When a call arrives:
- Exotel hits the inbound webhook → receives ExoML with
<Connect><Stream> - Exotel opens a WebSocket to
/ws/exotel/{agent_name}and streams PCM audio
Outbound Flow
POST /api/calls/outboundwith"provider": "exotel"- Server calls Exotel REST API (
/Calls/connect.json) - A
StatusCallbackURL is passed that returns ExoML when the call is answered - ExoML contains
<Connect><Stream>→ audio flows into the pipeline
Call Flow
Caller / Callee
↕
Exotel
↕ (WebSocket — linear PCM 8 kHz)
/ws/exotel/{agent}
↕
ExotelFrameSerializer ← resamples to pipeline rate
↕
Voice Pipeline (STT → LLM → TTS)
↕
ExotelFrameSerializer ← resamples back to 8 kHz
↕
Exotel → CallerVobiz
Required Settings
| Variable | Description |
|---|---|
VOBIZ_AUTH_ID | Auth ID from Vobiz |
VOBIZ_AUTH_TOKEN | Auth token from Vobiz |
PUBLIC_BASE_URL | Public HTTPS URL for webhooks |
Optional:
| Variable | Description |
|---|---|
VOBIZ_PHONE_NUMBER | Default phone number |
Inbound Setup
Configure your Vobiz number's inbound webhook:
POST https://your-domain.com/telephony/vobiz/inbound/{agent_name}The app returns Vobiz XML that connects to the WebSocket for audio streaming.
Outbound Flow
POST /api/calls/outboundwith"provider": "vobiz"- Server calls Vobiz REST API
- An
answer_urlcallback is passed that returns XML when the call is answered - XML connects the WebSocket → pipeline runs
Notes
- Vobiz uses a Plivo-compatible protocol
- The
VobizFrameSerializerhandles mu-law 8kHz encoding and supports auto hang-up - WebSocket parsing extracts metadata from query parameters (not from the WebSocket start message)
Concurrency Limits
Each provider has a configurable maximum concurrent call limit (default: 10).
- Enforced at WebSocket accept — returns 503 if the limit is reached
- Enforced at
POST /api/calls/outbound— returns an error if the limit is reached - Configurable per-provider in telephony settings
Public URL Requirements
All providers require a publicly accessible HTTPS URL for webhooks and WebSocket connections. For local development, use a tunnel:
bash
# ngrok
ngrok http 8000
# cloudflared
cloudflared tunnel --url http://localhost:8000Set the resulting URL:
env
PUBLIC_BASE_URL=https://your-tunnel-url.comThis URL is used to build:
https://.../telephony/{provider}/inbound/{agent}(webhook)wss://.../ws/{provider}/{agent}(WebSocket)