Telephony Integration (Twilio, etc.)
On this page
Updated November 3, 2026 based on the Realtime API documentation: the G.711 μ-law format is named
audio/pcmu(the text previously citedg711_ulaw, which was the beta name). Source: Realtime API.
November 2, 2026 update — Audio models. Several models in this course are deprecated:
tts-1,tts-1-hd, andgpt-4o-mini-ttswill stop working on January 6, 2027 (the indicated replacement isgpt-realtime-2.1-mini, which the OpenAI TTS guide states is not supported by/v1/audio/speech: speech synthesis migration must go through the Realtime API);gpt-realtime,gpt-4o-realtime, and legacy audio models on January 20, 2027 (gpt-realtime-2.1,gpt-realtime-2.1-mini,gpt-audio-1.5);whisper-1,gpt-4o-transcribe, and/v1/audio/translationson February 26, 2027 (gpt-transcribe,gpt-live-transcribe,v1/realtime/translations). For full-duplex voice, GPT-Live 1 has been available in the API since September 10, 2026.
Telephony Integration (Twilio, etc.)
A voice agent becomes truly useful when it’s accessible by phone. Cloud telephony platforms like Twilio, LiveKit, and Daily let you connect the Realtime API to the global telephone network. A user dials a number, and your agent answers in real time.
Telephony Architecture
Telephony integration adds a layer between the telephone network and the Realtime API:
Incoming Call → Twilio
The user dials a phone number. Twilio receives the call and sends a webhook to your server.
Your Server → Media Stream
Your server responds with TwiML instructions that open a bidirectional Media Stream WebSocket.
Media Stream ↔ Realtime API
Your server relays audio between the Twilio Media Stream and the OpenAI Realtime API.
Voice Response → User
The response audio from the Realtime API is sent back via Twilio to the user's phone.
Implementation with Twilio
Incoming Call Webhook
When a call arrives, Twilio sends an HTTP request to your server. You respond with TwiML that opens a Media Stream:
from fastapi import FastAPI, Request
from fastapi.responses import Response
app = FastAPI()
@app.post("/incoming-call")
async def incoming_call(request: Request):
"""Twilio webhook for incoming calls."""
twiml = """<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Say language="en-US">
Welcome to Corsen AI. Connecting you to your voice assistant.
</Say>
<Connect>
<Stream url="wss://your-server.com/media-stream">
<Parameter name="caller" value="{caller_number}" />
</Stream>
</Connect>
</Response>"""
form_data = await request.form()
caller = form_data.get("From", "unknown")
twiml = twiml.replace("{caller_number}", caller)
return Response(content=twiml, media_type="application/xml")
The Media Stream ↔ Realtime API Bridge
The WebSocket server receives audio from Twilio and relays it to the Realtime API:
from fastapi import WebSocket
import websockets
import json
import base64
import audioop
import asyncio
OPENAI_API_KEY = "sk-..."
REALTIME_URL = "wss://api.openai.com/v1/realtime?model=gpt-realtime-2.1"
@app.websocket("/media-stream")
async def media_stream(twilio_ws: WebSocket):
"""Bridge between Twilio Media Stream and the Realtime API."""
await twilio_ws.accept()
# Connect to the Realtime API
headers = {
"Authorization": f"Bearer {OPENAI_API_KEY}"
}
async with websockets.connect(REALTIME_URL, additional_headers=headers) as openai_ws:
await openai_ws.recv() # session.created
# Configure the session
await openai_ws.send(json.dumps({
"type": "session.update",
"session": {
"type": "realtime",
"output_modalities": ["audio"],
"instructions": "You are a phone assistant. "
"Respond in English, concisely.",
"audio": {
"input": {
"format": {"type": "audio/pcmu"},
"turn_detection": {
"type": "server_vad",
"threshold": 0.5,
"silence_duration_ms": 600
}
},
"output": {
"format": {"type": "audio/pcmu"},
"voice": "marin"
}
}
}
}))
await openai_ws.recv()
stream_sid = None
async def twilio_to_openai():
nonlocal stream_sid
async for message in twilio_ws.iter_text():
data = json.loads(message)
if data["event"] == "media":
audio_b64 = data["media"]["payload"]
await openai_ws.send(json.dumps({
"type": "input_audio_buffer.append",
"audio": audio_b64
}))
elif data["event"] == "start":
stream_sid = data["start"]["streamSid"]
async def openai_to_twilio():
async for message in openai_ws:
event = json.loads(message)
if event["type"] == "response.output_audio.delta":
await twilio_ws.send_json({
"event": "media",
"streamSid": stream_sid,
"media": {
"payload": event["delta"]
}
})
await asyncio.gather(twilio_to_openai(), openai_to_twilio())
G.711 Audio Format
Telephony uses the G.711 μ-law format (8 kHz, one byte per sample). The Realtime API supports this natively under the name audio/pcmu, which you declare for both input and output (audio.input.format and audio.output.format, as in the code above). This avoids any server-side conversion. The name g711_ulaw belonged to the API beta; the A-law equivalent is named audio/pcma.
Twilio Alternatives
| Platform | Strength | Realtime API Integration |
|---|---|---|
| Twilio | Complete ecosystem, numbers in 100+ countries | Native WebSocket Media Streams |
| LiveKit | Open source, low latency, WebRTC | Official OpenAI plugin |
| Daily | Simple, hosted, good for prototyping | Pipecat SDK with Realtime connector |
Key Takeaways
- Twilio Media Streams lets you connect a phone call to a bidirectional WebSocket
- Your server acts as a bridge between the Twilio Media Stream and the OpenAI Realtime API
- The G.711 μ-law format is native to telephony and directly supported by the Realtime API
- LiveKit (open source, WebRTC) and Daily (hosted) are viable alternatives to Twilio
- The architecture is always the same: telephony → your server → Realtime API