> ## Documentation Index
> Fetch the complete documentation index at: https://visionagents.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Telnyx

> Connect inbound and outbound PSTN phone calls via Telnyx Call Control and bidirectional media streaming

[Telnyx](https://telnyx.com/) 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.

<Info>
  Vision Agents uses [Stream Video](https://getstream.io/video/) for real-time WebRTC transport by default. [External WebRTC transports](/integrations/introduction-to-integrations#edge-transport) are supported as well. Most AI providers offer free tiers to get started.
</Info>

## 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)

```mermaid theme={null}
sequenceDiagram
    participant PSTN as PhoneNetwork
    participant Telnyx as TelnyxCallControl
    participant App as FastAPI_Server
    participant Plugin as telnyx_plugin
    participant Stream as StreamCall

    PSTN->>Telnyx: inbound_or_outbound_call
    Telnyx->>App: POST_/telnyx/events
    App->>Telnyx: answer_or_dial_with_stream_url
    Telnyx->>App: WS_/telnyx/media/{call_id}/{token}
    App->>Plugin: MediaStream.accept_and_run
    App->>Plugin: attach_phone_to_call
    Plugin->>Stream: bridge_audio_bidirectional
```

## Prerequisites

| Requirement                                     | Notes                                                                           |
| ----------------------------------------------- | ------------------------------------------------------------------------------- |
| [Telnyx account](https://telnyx.com/) + API key | `TELNYX_API_KEY`                                                                |
| Telnyx phone number (E.164)                     | `TELNYX_PHONE_NUMBER`                                                           |
| **Call Control App**                            | Required — a forwarding-only number connection is **not** enough                |
| Webhook public URL                              | [ngrok](https://ngrok.com/) for local dev → `https://<NGROK_URL>/telnyx/events` |
| Webhook signature key                           | `TELNYX_PUBLIC_KEY` (Base64 Ed25519 from Mission Control Portal)                |
| Stream credentials                              | `STREAM_API_KEY`, `STREAM_API_SECRET`                                           |

<Warning>
  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.
</Warning>

## Installation

```sh theme={null}
uv add "vision-agents[telnyx]"
```

Or install the plugin package directly:

```sh theme={null}
uv add vision-agents-plugins-telnyx
```

## Environment Variables

| Variable                               | Required         | Description                                                           |
| -------------------------------------- | ---------------- | --------------------------------------------------------------------- |
| `TELNYX_API_KEY`                       | Yes              | Telnyx API key for Call Control / Dial                                |
| `TELNYX_PUBLIC_KEY`                    | Yes (production) | Ed25519 public key for webhook signature verification                 |
| `TELNYX_PHONE_NUMBER`                  | Yes              | Caller ID / inbound number in E.164                                   |
| `NGROK_URL`                            | Local dev        | Public hostname without `https://`                                    |
| `TELNYX_CALL_CONTROL_APP_ID`           | Manual setup     | Existing Call Control App ID (skip if using example `--setup-telnyx`) |
| `TELNYX_PHONE_NUMBER_ID`               | Inbound manual   | Telnyx phone number resource ID                                       |
| `STREAM_API_KEY` / `STREAM_API_SECRET` | Yes              | Stream 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](https://github.com/GetStream/Vision-Agents/tree/main/plugins/telnyx/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:

```python theme={null}
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()
```

<Tip>
  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](https://github.com/GetStream/Vision-Agents/blob/main/plugins/telnyx/examples/inbound_call.py)
  for the Ed25519 verification pattern.
</Tip>

## 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:

```python theme={null}
@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:

```python theme={null}
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

| Component                             | Description                                                             |
| ------------------------------------- | ----------------------------------------------------------------------- |
| `TelnyxCallRegistry` / `CallRegistry` | Tracks active calls, tokens, optional async `prepare` tasks             |
| `TelnyxCall`                          | Call session with `from_number`, `to_number`, `await_prepare()`         |
| `TelnyxMediaStream` / `MediaStream`   | WebSocket media handler; exposes `audio_track`, `send_audio()`, `run()` |
| `attach_phone_to_call`                | Bridges Telnyx RTP audio ↔ Stream call participant                      |
| Audio helpers                         | `pcmu_to_pcm`, `pcm_to_pcmu`, `pcm_to_pcma`, `l16_to_pcm`, etc.         |

## Audio formats

| Encoding    | Sample rate | Direction                                        |
| ----------- | ----------- | ------------------------------------------------ |
| PCMU / PCMA | 8 kHz       | Default inbound; outbound with bidirectional RTP |
| L16         | 16 kHz      | Bidirectional when configured                    |

To send agent audio back to the caller, start Telnyx streaming with `stream_bidirectional_mode=rtp`.

| Constant                     | Default | Description            |
| ---------------------------- | ------- | ---------------------- |
| `TELNYX_DEFAULT_SAMPLE_RATE` | `8000`  | PCMU/PCMA rate         |
| `TELNYX_L16_SAMPLE_RATE`     | `16000` | L16 bidirectional rate |

## Common setup errors

| Error                     | Fix                                                                       |
| ------------------------- | ------------------------------------------------------------------------- |
| Invalid `connection_id`   | Check Call Control App ID is set and active                               |
| Webhook URL mismatch      | Update the app webhook to your current ngrok URL, or use `--setup-telnyx` |
| Inbound number not routed | Assign the Telnyx number to the Call Control App                          |
| Destination not verified  | Verify the `--to` number in Telnyx, or use an unrestricted account        |

## Next Steps

<CardGroup cols={2}>
  <Card title="Telnyx Plugin Examples" icon="github" href="https://github.com/GetStream/Vision-Agents/tree/main/plugins/telnyx/examples">
    Minimal inbound and outbound phone examples
  </Card>

  <Card title="Phone Calling" icon="phone" href="/guides/calling">
    Provider overview and learning path
  </Card>

  <Card title="Twilio" icon="phone" href="/integrations/telephony/twilio">
    Alternative telephony provider
  </Card>

  <Card title="Stream Video RTC" icon="signal-stream" href="/integrations/edge-transport/getstream">
    Default edge transport for agent calls
  </Card>

  <Card title="RAG for Agents" icon="magnifying-glass" href="/guides/rag">
    Add knowledge base to phone agents
  </Card>

  <Card title="Build a Voice Agent" icon="microphone" href="/introduction/voice-agents">
    Get started with voice
  </Card>
</CardGroup>
