---
title: "Use ThunderPhone with OpenAI Realtime clients"
description: "Point an OpenAI Realtime-compatible server client at ThunderPhone, using either a saved agent or inline session configuration."
---

ThunderPhone implements a focused subset of the OpenAI Realtime event model.
An existing server-side client can keep its WebSocket, audio, session, and
response event flow while using a ThunderPhone voice agent.

## Before you connect

You need a ThunderPhone organization secret API key and a server runtime that
can open WebSockets. Do not connect from browser code or expose the key to a
browser. To use a saved agent, deploy it first and copy its numeric agent ID.

Connect to:

```text
wss://api.thunderphone.com/v1/realtime
```

Authenticate the WebSocket upgrade from your server:

```http
Authorization: Bearer sk_live_YOUR_API_KEY
```

Query-string authentication exists for clients that cannot set handshake
headers, but URLs are easier to leak into logs.

## Choose who owns the configuration

| Mode | Connect with | Configuration source |
| --- | --- | --- |
| Saved agent | `?agent_id=12` | Deployed prompt, voice, product, languages, knowledge, and compatible server-run tools |
| Inline | No `agent_id` | The first accepted `session.update` from your client |

### Saved agent

Connect with the deployed agent ID:

```text
wss://api.thunderphone.com/v1/realtime?agent_id=12
```

The agent starts while the socket connects. Its greeting remains available,
but Realtime disables its spoken silence check-ins and omits `transfer_call`
and `send_keypad_input`. Other compatible tools run on ThunderPhone. Do not
send inline instructions or client-executed tools for this mode.

Set wire audio before the agent starts with `input_audio_format`,
`output_audio_format`, `input_rate`, and `output_rate` query parameters. You
cannot change the saved configuration or audio formats after connection.

### Inline session

Without `agent_id`, wait for `session.created`, then send `session.update`:

```json
{
  "type": "session.update",
  "session": {
    "type": "realtime",
    "instructions": "Answer questions clearly and keep responses brief.",
    "audio": {
      "input": {
        "format": { "type": "audio/pcm", "rate": 24000 }
      },
      "output": {
        "format": { "type": "audio/pcm", "rate": 24000 },
        "voice": "olivia"
      }
    },
    "config": {
      "product": "bolt"
    }
  }
}
```

The first accepted update provisions the call. `session.updated` means the
session is live. Instructions, voice, product, tools, and audio formats cannot
change after that point.

Inline sessions have no automatic greeting or spoken silence check-ins. To
make the agent speak first, append a system or user message and send
`response.create`. A completely idle session still ends at the platform's
silent-call limit, 600 seconds by default.

## Use the official OpenAI SDK

Pass a WebSocket base URL ending at `/v1`; the SDK appends `/realtime`. The
`model` value is a compatibility name and does not select the ThunderPhone
product. Choose the product in the session configuration or the saved agent.

This connection check creates an inline Bolt session, prints events until the
first `session.updated`, and closes. Use the [minimal Python
client](/api-reference/realtime#minimal-python-client) to stream audio.

```python
import asyncio
import os

from openai import AsyncOpenAI


async def main():
    client = AsyncOpenAI(
        api_key=os.environ["THUNDERPHONE_API_KEY"],
        websocket_base_url="wss://api.thunderphone.com/v1",
    )

    async with client.realtime.connect(
        model="thunderphone-realtime"
    ) as connection:
        await connection.session.update(session={
            "type": "realtime",
            "instructions": "Listen to the caller and help them complete the call.",
            "config": {"product": "bolt"},
        })
        async for event in connection:
            print(event.type)
            if event.type == "session.updated":
                break


if __name__ == "__main__":
    asyncio.run(main())
```

Keep your existing handling for input-audio append, response audio deltas,
interruptions, function calls, errors, and clean socket closure. Inline custom
functions execute in your client; return their results through the Realtime
protocol. Saved-agent tools execute on ThunderPhone.

## Session lifecycle and failures

One WebSocket represents one call. An invalid agent ID or rejected inline
configuration produces an `error` event. Wait for `session.updated` before
treating the session as live. Declare the actual input and output formats and
sample rates: a PCM rate mismatch plays audio too fast or too slowly instead of
producing a validation error.

After the session starts, close the socket cleanly when your application is
done. Inline sessions can use client-executed functions. Saved-agent sessions
use compatible ThunderPhone-run tools and do not offer transfer or keypad
input.

## Test the integration

Start with the [minimal Python WAV client](/api-reference/realtime#minimal-python-client)
and a mono PCM16 WAV at its declared rate. Verify:

1. The server accepts the configuration and sends `session.updated`.
2. Input produces transcript and response-audio events at the expected speed.
3. Interruption and response cancellation stop remaining output audio.
4. Inline function results or saved-agent tool results return to the model.
5. Invalid input produces an `error` event your client handles.
6. Your client closes the socket and the call appears in [Call
   history](/guides/review-calls).

The [Realtime WebSocket reference](/api-reference/realtime) lists the accepted
events, audio formats, session fields, and complete examples.

## Cost

Realtime calls use the selected product's normal per-minute rate. Enabling live
transcript deltas adds a per-minute surcharge for the whole session; see [Live
transcripts](/api-reference/realtime#live-transcripts) for the rate and
[Pricing](/guides/pricing) for product rates.

`POST /v1/realtime/sessions` is a separate managed LiveKit path. It creates a
room and scoped participant token; it is not required for a direct WebSocket
connection.

For framework integrations, see [Use ThunderPhone from Pipecat](/guides/use-with-pipecat)
and [Use ThunderPhone from LiveKit Agents](/guides/use-with-livekit).
