# Web call protocol (/docs/api-reference/web-call-protocol)

> The WebSocket protocol behind web call sessions, for iOS, Android, Flutter, kiosks, and any platform without an official SDK.

Web call sessions stream audio over a plain WebSocket with JSON frames.
In the browser, the `@omnidim-ai/client` SDK (see the SDKs tab) handles all
of this for you. Use this page when you are integrating from a platform
without an official SDK: mobile apps, desktop apps, kiosks, or embedded
devices.

## Get a connection URL [#get-a-connection-url]

Create a session server-side with your API key using
[create session](/docs/api-reference/sessions/createSession). The
response contains a `ws_url`:

```json
{
  "session_id": 4521,
  "token": "sess_51gF2qw8LxNz0vY4mT7Ka3RjD9pBcE6HuWiQnZsX0oM",
  "expires_at": "2026-07-17T12:15:00Z",
  "ws_url": "wss://live.omnidim.io/chat/start_voice_chat?request_token=sess_..."
}
```

Connect your client to `ws_url` exactly as returned. Never construct or
cache WebSocket URLs; the URL format is not a contract and can change
at any time.

Token semantics:

* The token is valid to connect for 15 minutes after create.
* It covers a single conversation and expires when that conversation
  ends.
* If the connection drops mid-call, reconnecting to the same `ws_url`
  re-attaches to the same conversation, so a brief network blip does
  not lose the call.

## Send: microphone audio [#send-microphone-audio]

Send one JSON frame per audio chunk:

```json
{ "type": "audio", "data": "<base64 of 16 kHz mono PCM16>" }
```

* Audio is 16 kHz, mono, 16-bit little-endian PCM, base64-encoded.
* Chunks of roughly 100 to 200 ms work well.
* Hang up by closing the socket. No other frame types are needed.

## Receive: agent events [#receive-agent-events]

Every server frame is a JSON object with an `event` field. Ignore any
event you do not recognize; new events may be added at any time.

| Event                 | Payload                                                           | What to do                                                                                                    |
| --------------------- | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| `media`               | `media.payload`: base64 16 kHz mono PCM16                         | Queue it for playback.                                                                                        |
| `clear`               | none                                                              | Stop playback and drop everything queued. The visitor interrupted the agent, so latency here is very visible. |
| `end_call`            | `media.payload.reason`: why the call ended (for example `hangup`) | Stop audio and close your UI. The server closes the socket.                                                   |
| `user`                | `media.payload`: text                                             | Final transcript of the visitor's speech so far this turn.                                                    |
| `partial_text`        | `media.payload`: text                                             | Interim (still changing) visitor transcript.                                                                  |
| `system`              | `media.payload`: text                                             | The agent's reply text as it starts speaking.                                                                 |
| `last_system_message` | `media.payload`: text                                             | The agent's full reply so far this turn. Each one supersedes the previous, so render the latest.              |
| `error`               | message text                                                      | Surface or log the error. The call may still continue.                                                        |

## Close codes [#close-codes]

| Code | Meaning                                                           |
| ---- | ----------------------------------------------------------------- |
| 1000 | Normal close: the conversation ended.                             |
| 1011 | Internal error.                                                   |
| 1013 | Try again later: the platform is at capacity. Retry with backoff. |

An invalid, expired, or already-used token closes the connection during
setup. Create a new session and connect with the fresh `ws_url`.