AI Summary: This article is a technical walkthrough of twilio migration from Twilio Programmable Voice to Teler, written for backend and API integration engineers evaluating the switch in 2026. Twilio’s bidirectional Media Streams require outbound audio as raw 8 kHz mu-law, base64-encoded, with no container headers, a constraint that shapes every downstream codec and resampling decision. Engineers must decide whether to bridge the existing Twilio call path or cut over entirely to Teler’s WebSocket transport. Teler handles carrier connectivity, raw media streaming, and interruption signaling so the application layer focuses on STT, LLM, and TTS rather than telephony plumbing.
Scope: This walkthrough covers the twilio migration path from Twilio Programmable Voice, specifically its REST call control, TwiML webhooks, and bidirectional Media Streams, to Teler’s WebSocket-based voice infrastructure, verified against Twilio’s API documentation as of September 2026 and Teler’s published specification. It does not cover Twilio Flex, Twilio Studio, or Twilio Conversations.
Quick Answer: Migrating from Twilio Programmable Voice to Teler in 2026 means replacing TwiML webhooks with Teler’s WebSocket call events, swapping Twilio’s <Connect><Stream> bidirectional stream for Teler’s real-time media socket, and ensuring all audio injected back into the call is 8 kHz, base64-encoded, and sent in chunks of at least 500 ms.
A twilio migration to Teler is primarily an audio pipeline swap: the carrier leg moves from Twilio’s infrastructure to Teler’s edge-distributed points of presence, and the WebSocket message contract changes from Twilio’s Connected, Start, Media, Mark, Clear, Stop sequence to Teler’s equivalent transport layer.
What does Twilio Programmable Voice do, and where does it stop?
Twilio Programmable Voice is a cloud voice API that makes, receives, routes, and manages phone calls across web browsers, SIP domains, and mobile applications. It occupies the carrier and call-control layer: Twilio owns the PSTN (Public Switched Telephone Network) leg, handles SIP (Session Initiation Protocol) signaling, and exposes call state through a REST API at base URL 2010-04-01 plus selected voice.twilio.com/v1 and v2 resources. What it does not do is run your speech recognition, your language model, or your text-to-speech engine. Those are your responsibility. For real-time audio access, Twilio exposes Media Streams over WebSocket (RFC 6455), but your server must handle the framing, codec conversion, and buffer management. Twilio does not provide a built-in VAD (Voice Activity Detection) contract or an automatic resampling path between its 8 kHz mu-law stream and the 24 kHz audio that models like OpenAI Realtime expect.
Why is twilio migration harder than swapping credentials?
The surface area of a twilio migration looks small until you hit the audio path. Twilio’s bidirectional Media Streams deliver audio in 20 ms chunks of raw 8 kHz mu-law (ITU-T G.711), base64-encoded, with no container headers. Any audio you send back must match that format exactly: raw mu-law at 8000 Hz, base64-encoded, no WAV or other container bytes, framed in 160-byte chunks representing 20 ms each. Sending the wrong format produces garbled audio at the start of every utterance, a failure mode reported by Deepgram’s integration team in August 2026.
Beyond the codec constraint, three operational gaps make the migration non-trivial. First, Twilio’s Clear message flushes queued outbound audio, but your application must also cancel the upstream TTS job simultaneously or the old sentence keeps playing. Second, Twilio sends media in 20 ms chunks while a downstream VAD needs roughly 300 to 500 ms to detect speech start, so forwarding chunks immediately drops the first syllable. Third, WebSocket connections that sit idle during silence can hit inactivity timeouts, and Twilio’s documentation does not specify a keepalive contract, so your server must emit heartbeat frames independently.
What does the audio path look like end to end?
The table below maps each hop in the Twilio path against the equivalent Teler hop, so you can see exactly where the ownership boundary shifts during a twilio migration.
| Hop | Twilio path | Teler path | Format / transport | Owner |
|---|---|---|---|---|
| Carrier to platform | Twilio PSTN ingress | Teler PSTN ingress | RTP / G.711 mu-law | Platform |
| Platform to your server | WebSocket, <Connect><Stream> | WebSocket, Teler media socket | JSON frames, base64 audio | Your server |
| Your server to STT | Raw 8 kHz mu-law, decoded | Raw 8 kHz or 16 kHz PCM | HTTP or WebSocket | Your server |
| LLM inference | Your choice | Your choice, model-agnostic | HTTP or WebSocket | Your server |
| TTS to platform | 8 kHz mu-law, base64, no headers | 8 kHz base64, at least 500 ms chunks | WebSocket JSON frame | Your server |
| Platform to carrier | Twilio PSTN egress | Teler PSTN egress | RTP / G.711 mu-law | Platform |
The critical difference is the resampling requirement. If you use OpenAI Realtime, its native sample rate is 24 kHz. Teler streams at 8 kHz by default (configurable to 16 kHz). You must resample in both directions: downsample from 24 kHz to 8 kHz before sending to the telephony layer, and upsample from 8 kHz to 24 kHz before sending to the model. This hop is where most teams discover their latency problem, because a naive resampling implementation adds 40 to 80 ms per direction.
What do you need before starting the migration?
- A Teler account with API key, available at platform.frejun.ai (prototyping credits available without a credit card)
- At least one phone number provisioned through Teler’s number API (50+ countries supported)
- Your existing Twilio
TWILIO_ACCOUNT_SIDandTWILIO_AUTH_TOKENfor the parallel-run phase - A WebSocket server reachable over TLS, because both Twilio and Teler require
wss://endpoints - An audio resampling library:
audioop(Python stdlib, deprecated in 3.13) orresampy/librosafor Python, ornode-audioworkletfor Node.js - A mu-law encoder if your TTS engine outputs PCM:
audioop.lin2ulawin Python or an equivalent - Network access from your WebSocket server to Teler’s edge nodes and to your STT, LLM, and TTS providers
- Familiarity with Teler’s WebSocket message contract, documented at Teler API docs
How do you execute the twilio migration step by step?
Step 1: Provision a Teler number and configure the WebSocket endpoint
Provision a DID (Direct Inward Dialing) number through Teler’s number API and point its inbound webhook at your WebSocket server. Teler will POST a call event to your HTTP endpoint when a call arrives, then upgrade to WebSocket for the media stream. This replaces Twilio’s TwiML webhook plus <Connect><Stream> verb pair.
import asyncio
import websockets
import json
import base64
async def handle_call(websocket):
async for message in websocket:
event = json.loads(message)
event_type = event.get("type")
if event_type == "start"
stream_id = event["stream_id"]
print(f"Stream started: {stream_sid}")
elif event_type == "audio":
# Incoming audio: 8 kHz mu-law, base64-encoded
audio_bytes = base64.b64decode(event["data"]["audio_b64"])
await process_audio(audio_bytes)
async def main():
async with websockets.serve(handle_call, "0.0.0.0", 8765):
await asyncio.Future()
asyncio.run(main()) Watch the event_type field: Teler’s event names differ from Twilio’s Connected / Start sequence, so map them explicitly rather than assuming string equality.
Step 2: Decode inbound audio and buffer before forwarding to STT
Teler streams inbound audio as base64-encoded 8 kHz mu-law by default. Decode it, convert to linear PCM, then buffer at least 400 ms before forwarding to your STT engine. Forwarding 20 ms chunks immediately causes the first syllable to be dropped because the VAD needs 300 to 500 ms to detect speech start, as reported by Callstack.tech in January 2026.
BUFFER_MS = 400
BYTES_PER_MS = 16 # 8000 Hz x 2 bytes (16-bit PCM)
buffer = bytearray()
def process_audio(pcm_bytes: bytes) -> bytes | None:
global buffer
buffer.extend(pcm_bytes)
if len(buffer) >= BUFFER_MS * BYTES_PER_MS:
chunk = bytes(buffer)
buffer = bytearray()
return chunk
return None The audioop.ulaw2lin call takes a width argument in bytes per sample; use 2 for 16-bit PCM. If your STT engine expects 16 kHz, resample the PCM output before sending.
Step 3: Inject TTS audio back into the call
Teler expects outbound audio as base64-encoded 8 kHz mu-law, sent in chunks of at least 500 ms. Strip any WAV or container headers from your TTS output before encoding. Sending container bytes produces garbled audio at the start of every utterance. The chunk size is configurable from 20 to 2000 ms in multiples of 20, with a default of 400 ms, but FreJun recommends sending back at least 500 ms to avoid choppy playback.
import audioop, base64, json, uuid
def encode_for_teler(pcm_16k_bytes: bytes) -> tuple[str, str]:
pcm_8k, _ = audioop.ratecv(pcm_16k_bytes, 2, 1, 16000, 8000, None)
chunk_id = uuid.uuid4().hex
message = json.dumps({
"type": "audio",
"audio_b64": base64.b64encode(pcm_8k).decode("utf-8"),
"chunk_id": chunk_id,
})
return message, chunk_id
async def send_audio(websocket, pcm_bytes: bytes) -> str:
message, chunk_id = encode_for_teler(pcm_bytes)
await websocket.send(message)
return chunk_id
async def send_audio(websocket, pcm_bytes: bytes, stream_sid: str):
message = encode_for_teler(pcm_bytes, stream_sid)
await websocket.send(message) Confirm that audioop.ratecv receives the correct state argument on subsequent calls; passing None resets the resampler state and introduces a click at chunk boundaries.
Step 4: Implement barge-in and interruption handling
When the caller speaks while the agent is playing audio, you must flush Teler’s outbound buffer, cancel the upstream TTS job, and drop any late frames already in flight. Teler’s {"type": "clear"} message flushes queued playback on the telephony side. Sending only the clear message without canceling the TTS generator lets the old sentence keep playing, a failure mode reported in three separate sources including the OpenAI Community forum in January 2026.
import asyncio
tts_task: asyncio.Task | None = None
async def handle_barge_in(websocket):
global tts_task
# 1. Cancel the TTS generator
if tts_task and not tts_task.done():
tts_task.cancel()
# 2. Flush Teler's outbound audio buffer
await websocket.send(json.dumps({"type": "clear"}))
# 3. Reset transcript state to avoid stale context
reset_transcript_state() The order matters: cancel the TTS task before sending clear, so no new frames arrive at the WebSocket after the flush.
Step 5: Add WebSocket keepalive and reconnect logic
WebSocket connections that sit idle during silence can hit inactivity timeouts. Twilio’s documentation does not specify a keepalive interval, and Teler’s specification does not publish one either. Emit a heartbeat frame every 10 seconds and implement client-side reconnect with replay logic to avoid mid-call stream drops, a pattern reported by Callstack.tech in January 2026.
async def keepalive(websocket):
while True:
await asyncio.sleep(10)
try:
await websocket.ping() # standard WebSocket ping; Teler has no JSON ping message
except websockets.exceptions.ConnectionClosed:
break Run keepalive as a concurrent task alongside your main message loop, and cancel it when the call ends.
The reference implementation for Twilio Media Streams is available at github.com/twilio/media-streams, with Python, Node.js, Java, and Ruby examples. The repository was last updated on 11 September 2026 according to search metadata. Use it to validate your Twilio-side audio handling before cutting over to Teler. Teler’s own integration patterns are documented at Teler’s developer documentation.
Most teams discover their latency problem is not the model, it is the resampling hop they added between the carrier and the STT engine. A naive implementation of 24 kHz to 8 kHz conversion adds 40 to 80 ms per direction, and that compounds with every other hop in the pipeline. Fix the resampler first, then tune the buffer.
– Subhash Kalluri, Founder & CEO, FreJun
What breaks in production after a twilio migration?
| Symptom | Root cause | Fix | Frequency |
|---|---|---|---|
| Garbled audio at utterance start | Container/header bytes sent in media payload, or non-mu-law codec | Strip headers, base64-encode raw mu-law only, frame to 160-byte 20 ms chunks | Reported once (Deepgram, Aug 2026) |
| First syllable clipped | 20 ms chunks forwarded immediately; VAD needs 300 to 500 ms to detect speech | Buffer 400 ms minimum before forwarding to STT | Reported once (Callstack.tech, Jan 2026) |
| Old sentence keeps playing after barge-in | Only one side of the pipeline signaled: Teler buffer cleared but TTS not canceled, or vice versa | Cancel TTS task first, then send {"type": "clear"}, then reset transcript state | Reported three times |
| Stream drops mid-call during silence | WebSocket inactivity timeout, no keepalive | Emit heartbeat every 10 s, implement reconnect with replay | Reported once (Callstack.tech, Jan 2026) |
| Duplicate chunks or stale transcript state | Race condition between stream callbacks and STT/agent events on reconnect | Add processing guard, make downstream actions idempotent, reset state on barge-in | Reported twice |
| Choppy or robotic audio | Packet loss or jitter in the RTP path between endpoints | Move off lossy Wi-Fi, prioritize RTP traffic, consider larger jitter buffers | Reported twice (Twilio Support, Telphi Consulting) |
The barge-in failure catches the most teams because it requires coordinating three separate systems: the telephony buffer, the TTS generator, and the transcript state machine. Teams that fix only the Teler clear message without canceling the TTS job find that the caller hears the tail of the previous response even after interrupting. The fix is sequential: cancel first, clear second, reset third.
One honest limitation of Teler worth stating: the transport layer targets sub-100ms packet delivery as a design specification, but the documentation does not publish a measured benchmark methodology or a per-region latency table. If your application has a hard latency SLA, you should run your own end-to-end timing tests before committing to production traffic. See why latency is a critical factor in voice API integration deployment for the variables that affect that number in practice.
Which architecture should you choose for the twilio migration?
| Approach | Media terminates at | Latency cost | Operational burden | Lock-in |
|---|---|---|---|---|
| TwiML webhooks only (no streams) | Twilio / Teler platform | One HTTP round trip per turn | Low: stateless webhook handler | Platform TTS and routing verbs |
| Unidirectional stream (listen only) | Your server (analysis sidecar) | One extra hop for analytics | Medium: streaming processing pipeline | Platform owns PSTN leg |
| Bidirectional stream (full agent) | Your WebSocket server | Resampling plus buffer management | High: codec, buffer, barge-in, keepalive | Platform owns PSTN leg only |
| SIP trunking to your media server | Your SIP / RTP infrastructure | SIP signaling overhead | High: SIP, RTP, codec, PBX or SBC ops | Lowest: you own the media path |
For most AI voice agent workloads, bidirectional streaming is the right choice because it gives your server full control over what the caller hears and when. Choose SIP trunking instead if you already operate a PBX or SBC (Session Border Controller) and want to own the RTP media path end to end, as described in SIP Trunking vs Elastic SIP Trunking: What’s the Difference and Why It Matters. Choose the webhook-only approach if your use case is IVR routing or simple call deflection with no real-time audio processing. Teler supports all four patterns, so the architecture decision is yours rather than a platform constraint.
For teams building AI phone agents at scale, the bidirectional streaming pattern combined with Teler’s model-agnostic transport is the most future-proof path. The top CPaaS providers in the US comparison covers how Teler’s approach differs from other platforms on this axis.
Frequently asked questions
Does Teler accept the same audio format as Twilio Media Streams?
Both platforms use base64-encoded 8 kHz mu-law audio over WebSocket for the telephony leg, so the codec requirement is the same. The difference is in the message envelope: Teler’s JSON frame structure and event type names differ from Twilio’s Connected, Start, Media, Mark, Clear, Stop sequence. You must remap your message handlers explicitly rather than assuming string equality between the two contracts.
What happens if I send audio with WAV headers to Teler?
The call audio will be garbled at the start of every utterance. Teler, like Twilio, expects raw mu-law bytes with no container headers. Strip the WAV header before base64-encoding. Deepgram’s integration team documented this failure mode in August 2026 specifically for Twilio, and the same constraint applies to any platform that expects raw telephony audio.
Should I use SIP trunking or WebSocket streaming for the migration?
Use WebSocket streaming if you are building an AI voice agent that needs to inject synthesized audio into the live call. Use SIP trunking if you already operate a media server or PBX and want to own the RTP (Real-time Transport Protocol, RFC 3550) path. Teler supports both, with SIP credential or IP authentication and IP access control lists for the SIP path.
How does Teler handle high call volumes during the migration?
Teler’s edge-distributed points of presence handle carrier connectivity and media routing, so your application server is not in the path for PSTN signaling. Your WebSocket server still needs to handle concurrent connections, and you must ensure your STT, LLM, and TTS providers can absorb the concurrent request load. Teler’s documentation does not publish a concurrency cap, so test your pipeline under realistic load before cutting over production traffic. See which voice calling API providers handle platform-scale traffic for the architectural patterns that matter at scale.
What must I build myself that Twilio provided out of the box?
Both Twilio and Teler leave STT, LLM, and TTS to you. What changes in the migration is the call control vocabulary: you replace TwiML verbs like <Say>, <Gather>, and <Dial> with Teler’s WebSocket call control messages for hangup, mute, DTMF, play audio, and cold transfer. You also build your own barge-in coordinator, because neither platform provides a built-in interruption handler that spans the telephony buffer and the TTS generator simultaneously.
Can I run Twilio and Teler in parallel during the migration?
Yes. Route a subset of your DIDs to Teler while keeping the rest on Twilio, and compare call quality and latency metrics before full cutover. Keep your TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN active during the parallel-run phase. The main risk is transcript state divergence if your application shares a single state store across both call paths, so isolate state by call source during the overlap period.
Does Teler work with OpenAI Realtime, Gemini Live, and other models?
Teler is model-agnostic and documented as compatible with OpenAI Realtime, Gemini Live, ElevenLabs, Deepgram, Vapi, Ultravox, Sarvam, Devnagri, and LiveKit. The resampling requirement is model-specific: OpenAI Realtime operates at 24 kHz, so you must downsample from 24 kHz to 8 kHz before sending to Teler and upsample from 8 kHz to 24 kHz before sending to the model. See Building Bidirectional Streaming Voice Agents Using Teler with Gemini Live for a worked example.
What Opus or wideband codec options are available on the telephony leg?
The PSTN leg uses G.711 mu-law at 8 kHz in both Twilio and Teler, because that is the narrowband telephony standard defined in ITU-T G.711. Opus (RFC 6716) is not available on the PSTN leg. If you need wideband audio, configure Teler’s stream sample rate to 16 kHz, which gives you higher-fidelity audio on the WebSocket path while the carrier leg remains at 8 kHz.
What to do next with your twilio migration
A twilio migration to Teler is achievable in a single sprint if you treat it as an audio pipeline swap rather than a full platform replacement. The decision that matters most is whether you use bidirectional WebSocket streaming or SIP trunking: streaming is the right default for AI voice agents, and SIP is the right choice only if you already operate your own media infrastructure. If your use case involves real-time interruption handling, model-agnostic LLM routing, or sub-100ms latency targets, Teler’s transport layer is built for that workload. If your primary need is simple IVR routing with no raw audio access, the webhook-only approach on either platform is simpler to operate. Start with a parallel run on a subset of your numbers, validate your resampling and barge-in logic under load, then cut over.