Skip to main content
Telnyx is a programmable telephony provider. The telnyx plugin bridges PSTN phone calls into a Vision Agents + Stream call through Telnyx Call Control webhooks and bidirectional Media Streaming.
Vision Agents requires a Stream account for real-time transport.

What the plugin provides

  • Call Control webhook handling pattern (via your FastAPI server)
  • Bidirectional Telnyx Media Streaming over WebSocket
  • CallRegistry for call/session/token tracking
  • attach_phone_to_call to bridge Telnyx audio ↔ Stream WebRTC
  • Audio conversion: PCMU, PCMA (8 kHz), L16 (16 kHz)

Prerequisites

RequirementNotes
Telnyx account + API keyTELNYX_API_KEY
Telnyx phone number (E.164)TELNYX_PHONE_NUMBER
Call Control AppRequired — a forwarding-only number connection is not enough
Webhook public URLngrok for local dev → https://<NGROK_URL>/telnyx/events
Webhook signature keyTELNYX_PUBLIC_KEY (Base64 Ed25519 from Mission Control Portal)
Stream credentialsSTREAM_API_KEY, STREAM_API_SECRET
Telnyx media streaming for programmable calls requires a Call Control App with a webhook URL. A regular phone-number connection — including a forwarding-only connection — is not enough. The Call Control App ID is also the connection_id used by the outbound Dial API.

Installation

uv add "vision-agents[telnyx]"
Or install the plugin package directly:
uv add vision-agents-plugins-telnyx

Environment Variables

VariableRequiredDescription
TELNYX_API_KEYYesTelnyx API key for Call Control / Dial
TELNYX_PUBLIC_KEYYes (production)Ed25519 public key for webhook signature verification
TELNYX_PHONE_NUMBERYesCaller ID / inbound number in E.164
NGROK_URLLocal devPublic hostname without https://
TELNYX_CALL_CONTROL_APP_IDManual setupExisting Call Control App ID (skip if using example --setup-telnyx)
TELNYX_PHONE_NUMBER_IDInbound manualTelnyx phone number resource ID
STREAM_API_KEY / STREAM_API_SECRETYesStream edge transport

Telnyx account setup

Quick local dev (--setup-telnyx) — the plugin examples auto-create a temporary Call Control App, set the webhook URL, route the inbound number, and clean up on shutdown. Manual setup:
  1. Create a Telnyx Call Control App.
  2. Set the app webhook URL to https://<NGROK_URL>/telnyx/events.
  3. Route your inbound phone number to that Call Control App.
  4. For outbound calls, ensure the app has an outbound voice profile for your target country. On restricted accounts, verify destination numbers before dialing.
See the full setup guide in the Telnyx plugin examples on GitHub.

Quick Start

The plugin gives you registry, media stream, and bridge primitives. Your FastAPI server wires them to Telnyx webhooks and WebSockets:
from vision_agents.plugins import telnyx

registry = telnyx.CallRegistry()

# 1. Register call from webhook handler
call = registry.create(
    call_control_id,
    webhook_data=webhook_data,
    prepare=prepare_call,  # optional: pre-warm agent + Stream call
)

# 2. Answer/dial with media stream URL
stream_url = f"wss://{NGROK_URL}/telnyx/media/{call_control_id}/{call.token}"

# 3. WebSocket handler
stream = telnyx.MediaStream(websocket)
await stream.accept()
call.telnyx_stream = stream

agent, phone_user, stream_call = await call.await_prepare()
await telnyx.attach_phone_to_call(stream_call, stream, phone_user.id)
await stream.run()
Webhook signature verification is shown in the plugin examples (example_helpers.parse_verified_telnyx_webhook) but is application-level — not exported from the plugin’s public API. See the inbound example source for the Ed25519 verification pattern.

Inbound calls

Telnyx sends webhooks to POST /telnyx/events. On call.initiated with direction incoming, register the call and answer with a media stream URL:
@app.post("/telnyx/events")
async def telnyx_events(request: Request):
    data = await parse_verified_telnyx_webhook(request, telnyx_public_key)
    event_type = data["data"]["event_type"]
    payload = data["data"]["payload"]

    if event_type == "call.initiated" and payload["direction"] == "incoming":
        call_control_id = payload["call_control_id"]
        telnyx_call = registry.create(
            call_control_id,
            webhook_data=data,
            prepare=lambda: prepare_call(call_control_id),
        )
        stream_url = f"wss://{NGROK_URL}/telnyx/media/{call_control_id}/{telnyx_call.token}"
        await telnyx_client.answer_call(call_control_id, stream_url=stream_url)

    return {"ok": True}


@app.websocket("/telnyx/media/{call_id}/{token}")
async def media_stream(websocket: WebSocket, call_id: str, token: str):
    telnyx_call = registry.validate(call_id, token)
    stream = telnyx.MediaStream(websocket)
    await stream.accept()
    # attach to agent and run stream (see Quick Start)

Outbound calls

Pre-register the call in the registry, start your server, then dial via the Telnyx Call Control API with connection_id set to your Call Control App ID:
call_id = str(uuid.uuid4())
telnyx_call = registry.create(call_id, prepare=lambda: prepare_call(call_id))
stream_url = f"wss://{NGROK_URL}/telnyx/media/{call_id}/{telnyx_call.token}"

await telnyx_client.dial(
    to=to_number,
    from_=from_number,
    connection_id=call_control_app_id,
    stream_url=stream_url,
)
The WebSocket media handler is the same as inbound.

Key Components

ComponentDescription
TelnyxCallRegistry / CallRegistryTracks active calls, tokens, optional async prepare tasks
TelnyxCallCall session with from_number, to_number, await_prepare()
TelnyxMediaStream / MediaStreamWebSocket media handler; exposes audio_track, send_audio(), run()
attach_phone_to_callBridges Telnyx RTP audio ↔ Stream call participant
Audio helperspcmu_to_pcm, pcm_to_pcmu, pcm_to_pcma, l16_to_pcm, etc.

Audio formats

EncodingSample rateDirection
PCMU / PCMA8 kHzDefault inbound; outbound with bidirectional RTP
L1616 kHzBidirectional when configured
To send agent audio back to the caller, start Telnyx streaming with stream_bidirectional_mode=rtp.
ConstantDefaultDescription
TELNYX_DEFAULT_SAMPLE_RATE8000PCMU/PCMA rate
TELNYX_L16_SAMPLE_RATE16000L16 bidirectional rate

Common setup errors

ErrorFix
Invalid connection_idCheck Call Control App ID is set and active
Webhook URL mismatchUpdate the app webhook to your current ngrok URL, or use --setup-telnyx
Inbound number not routedAssign the Telnyx number to the Call Control App
Destination not verifiedVerify the --to number in Telnyx, or use an unrestricted account

Next Steps

Telnyx Plugin Examples

Minimal inbound and outbound phone examples

Phone Calling

Provider overview and learning path

Twilio

Alternative telephony provider

Stream Video RTC

Default edge transport for agent calls

RAG for Agents

Add knowledge base to phone agents

Build a Voice Agent

Get started with voice