Skip to main content
Lesson 15 of 18 · Realtime API and Voice Agents

Telephony Integration (Twilio, etc.)

Lesson 15 of 18 Updated November 3, 2026 Developer
On this page
  1. Telephony Integration (Twilio, etc.)
  2. Telephony Architecture
  3. Implementation with Twilio
  4. Twilio Alternatives
  5. Key Takeaways

Updated November 3, 2026 based on the Realtime API documentation: the G.711 μ-law format is named audio/pcmu (the text previously cited g711_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, and gpt-4o-mini-tts will stop working on January 6, 2027 (the indicated replacement is gpt-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/translations on 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:

1

Incoming Call → Twilio

The user dials a phone number. Twilio receives the call and sends a webhook to your server.

2

Your Server → Media Stream

Your server responds with TwiML instructions that open a bidirectional Media Stream WebSocket.

3

Media Stream ↔ Realtime API

Your server relays audio between the Twilio Media Stream and the OpenAI Realtime API.

4

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