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

# Agent Stream WebSocket

> Real-time voice agent calls over a WebSocket: stream 8 kHz call audio in, the agent's voice comes back

# Agent Stream WebSocket

Connect call audio you already carry over a WebSocket (a PBX, a telephony platform such as Twilio, Exotel or Klicko, or your own app) to a 60db **voice agent**. The agent listens, answers in its voice, uses its [tools](#tools-during-a-stream-call) (your Webhook (JSON) tools and Call transfer) and knowledge base, and can press keys, transfer to a person or hang up. No phone line or UDP ports are needed: everything runs over HTTPS, port 443.

## 🚀 Quick Start

### 1. Start the call (from your server)

```bash theme={null}
curl -X POST https://api.60db.ai/agents/$AGENT_ID/stream-calls \
  -H "Authorization: Bearer $SIXTYDB_API_KEY" \
  -H "Content-Type: application/json" -d '{}'
```

```json theme={null}
{
  "success": true,
  "data": {
    "call": { "id": "6b1f0c52-…", "direction": "stream", "status": "dialing" },
    "url": "wss://api.60db.ai/ws/agents/stream/6b1f0c52-…?token=…",
    "expires_in": 55
  }
}
```

The `url` is **single use** and valid for **55 seconds**. It carries no API key, so you can hand it to the component that streams audio. See [Start stream call](https://docs.60db.ai/api-reference/agents/start-stream-call.md) for `overrides` and `metadata`.

### 2. Connect and stream (Node.js 22+)

```javascript theme={null}
const ws = new WebSocket(url);
ws.binaryType = "arraybuffer";

ws.onopen = () => ws.send(JSON.stringify({ event: "start", format: "mulaw" })); // within 10 s

ws.onmessage = ({ data }) => {
  if (typeof data !== "string") return play(new Uint8Array(data));       // agent audio, 20 ms frames
  const m = JSON.parse(data);
  if (m.event === "started") startSending((frame) => ws.send(frame));   // a 160-byte frame every 20 ms
  if (m.event === "clear") flushPlayback();                              // caller cut in: drop unplayed audio
  if (m.event === "transfer") transferCaller(m.destination, m.numbers)
    .then((outcome) => ws.send(JSON.stringify({ event: "transfer_result", outcome })));
  if (m.event === "ended") console.log(m.summary);
};
```

## 🔌 Connection

| Item | Value |
| - | - |
| URL | `wss://api.60db.ai/ws/agents/stream/{callId}?token=…` (from step 1) |
| Auth | The one-time `token` in the URL. The first connection spends it, right or not |
| First message | Within **10 seconds** of connecting |
| Message size | Under **4 KB** |
| Keep-alive | The server pings every 15 s; a connection silent for 45 s is closed |

## 🎧 Styles

The style is picked from **your first message**.

### Native style

```json theme={null}
{ "event": "start", "format": "mulaw" }
```

| Format | Samples | Bytes per 20 ms frame |
| - | - | - |
| `mulaw` | G.711 μ-law, 8 kHz mono | 160 |
| `pcm16` | signed 16-bit little endian, 8 kHz mono | 320 |

You get `{"event":"started","call_id":"…","format":"mulaw","frame_bytes":160}`, then audio as **binary** messages in both directions.

### Twilio Media Streams style

Send your usual Twilio, Exotel or Klicko messages: no adapter, no change to the request.

```json theme={null}
{ "event": "connected" }
{ "event": "start", "start": { "streamSid": "MZ…", "mediaFormat": { "encoding": "audio/x-mulaw", "sampleRate": 8000, "channels": 1 } } }
{ "event": "media", "media": { "payload": "<base64 20 ms>" } }
```

* `mediaFormat` may be nested in `start` (Twilio) or at the top of it (Klicko).
* Encodings: `audio/x-mulaw`, `pcmu`, `mulaw` (μ-law) or `audio/x-l16`, `l16`, `pcm16`, `pcm16_8k` (PCM16). The rate must be 8000.
* `pcma` and 16 kHz formats close the socket with `1008`.
* There is no `started` reply. Audio comes back as `{"event":"media","streamSid":"…","streamId":"…","media":{"payload":"…"}}`.
* `mark` is ignored. Base64 in JSON is about 1.7 times the native bytes.

## 📨 Messages

### From the agent

| Message | Meaning |
| - | - |
| `started` | The call began (native style only) |
| `media` / binary | The agent's audio, paced in real time |
| `{"event":"clear"}` | The caller cut in: drop agent audio you have not played yet |
| `{"event":"dtmf","digits":"5"}` | The agent pressed a key on the caller's phone (agent setting `dtmf: true`) |
| `{"event":"transfer","destination":"Sales","numbers":["+919800000001"]}` | Put the caller through to a person; the first number is the one to try first |
| `{"event":"hangup"}` | The agent is hanging up |
| `{"event":"ended","summary":{…}}` | The call is over: turns, sentiment, transfer, interruptions, VAD, latency. The socket then closes with `1000` |

### From you

| Message | Meaning |
| - | - |
| binary / `media` | Caller audio. Send in real time, and send silence while the caller is quiet |
| `{"event":"dtmf","digits":"5#"}` | The caller pressed keys: `0-9`, `*`, `#`, `A-D`, up to 32 (Twilio style also accepts `{"dtmf":{"digit":"5"}}`) |
| `{"event":"transfer_result","outcome":"joined"}` | How a transfer went: `joined`, `busy`, `declined`, `no_answer` or `failed`, within 10 s |
| `{"event":"stop"}` | The caller hung up. Closing the socket does the same |

## Tools During a Stream Call

A stream call uses the agent's tools exactly like a phone call. Add them on the agent (dashboard → agent → **Tools & webhooks** → **Add tool**, or `tools` / `transfer_targets` in the API).

### Webhook (JSON) tools

Your own functions, such as checking availability or booking a table. They **do not go over this socket**. 60db calls your server directly:

1. Define the tool on the agent with a JSON Schema, and set the agent's **Tool webhook** (`tool_webhook_url`):

```json theme={null}
{
  "name": "book_table",
  "description": "Book a table once the caller gives a date, time and party size.",
  "parameters": {
    "type": "object",
    "properties": {
      "date": { "type": "string", "description": "YYYY-MM-DD" },
      "time": { "type": "string", "description": "HH:MM, 24-hour" },
      "guests": { "type": "integer", "minimum": 1 }
    },
    "required": ["date", "time", "guests"]
  }
}
```

2. During the call, when the agent uses it, 60db sends a signed `POST` to your `tool_webhook_url` with the tool name and arguments (headers `X-60db-Signature`, `X-60db-Timestamp`).
3. Reply with any JSON object within about 6 seconds, e.g. `{ "booked": true, "table": 12 }`. The agent turns it into its spoken answer. A slow or failed reply reaches the agent as `{"error": "tool_unavailable", …}`, so it can apologise.

The request and reply formats, signature checks and example servers (Node.js, Python) are in [Agent Tools and Webhooks](https://docs.60db.ai/features/agents-tools-and-webhooks.md#define-tools). An agent with tools must have a `tool_webhook_url`, or starting the call returns `400 VALIDATION_ERROR` (`field: tool_webhook_url`).

### Call transfer tool

Built in: no webhook needed. Set `transfer_targets` on the agent (or in `overrides` for one call). When the caller asks for a person, the agent says a short line and **this socket** receives:

```json theme={null}
{ "event": "transfer", "destination": "Sales", "numbers": ["+919800000001"] }
```

Put the caller through on your platform, then reply within 10 seconds with `{"event":"transfer_result","outcome":"joined"}` (or `busy`, `declined`, `no_answer`, `failed`). See [Call transfer](https://docs.60db.ai/features/agents-tools-and-webhooks.md#call-transfer).

### Keypad

With `call_defaults.dtmf: true` the agent can press keys (`{"event":"dtmf","digits":"1"}` arrives on this socket) and collect the caller's keys (send `{"event":"dtmf","digits":"5#"}`).

## ❌ Close Codes

| Code | Meaning |
| - | - |
| `1000` | The call ended normally |
| `1002` | A malformed or unknown message (native style) |
| `1008` | Invalid or used URL, no valid first message within 10 s, or an unsupported audio format |
| `1009` | A message over 4 KB |
| `1011` | The call service failed |

## 📊 Limits and Pricing

| Item | Value |
| - | - |
| Live stream calls | 3 at once per workspace (including calls waiting for their socket) |
| Call length | `max_duration_secs`, 10 to 900 (default 180) |
| Price | AI credits only, about **\$0.016 per minute** of conversation. No line charge |

After the call, find it in **Agents → History** (label **Stream**), or use [Get call](https://docs.60db.ai/api-reference/agents/get-call.md), [Get transcript](https://docs.60db.ai/api-reference/agents/get-transcript.md) and [Get recording](https://docs.60db.ai/api-reference/agents/get-recording.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.