# Subscribe to events over WebSocket (/en/docs/api/native/agent/streamEventsWebSocket)



Besides pulling session events with [Stream events](/en/docs/api/native/agent/streamEvents) (SSE), you can receive them over a WebSocket on **the same address**: one frame per event, with exactly the same event data as SSE. This is an access mode the platform offers in addition to the official SSE, suited to a server that keeps a long-lived connection and forwards events into your own system.

```text
wss://api.deepraxis.com/agent/v1/sessions/{session_id}/events/stream
```

The connection is **read-only**: it only pushes events and accepts no messages. Sending messages and interrupting still go through [Send events](/en/docs/api/native/agent/sendEvents).

## Handshake and authentication [#handshake-and-authentication]

The handshake carries the same auth headers as the HTTP endpoints: `Authorization: Bearer <API Key>` or `x-api-key: <API Key>`.

A browser's native `WebSocket` cannot set request headers, so this is a **server-side** surface. In a browser, use SSE, or let your server open the connection and relay it to the front end.

When the handshake fails, no connection is opened and the server answers with an HTTP error (body format in [Errors](/en/docs/api/errors)):

| Case                                                          | Response                                           |
| ------------------------------------------------------------- | -------------------------------------------------- |
| No credential, or the key is invalid or disabled              | `401`                                              |
| The session does not exist, or is not in this key's workspace | `404`                                              |
| In-flight requests exceed the key's concurrency limit         | `429`, see [Rate limits](/en/docs/api/rate-limits) |

An open WebSocket, like an in-progress SSE, holds one of the key's concurrency slots until it closes.

## Frames [#frames]

Every frame is one JSON text. After connecting, the first frame is a ready notice:

```json
{"type":"session.events.ready"}
```

After that, each event produced in the session arrives as one frame, and its `event` field is the SSE event data:

```json
{"type":"session.event","event":{"id":"d4dc3d96-…","type":"agent.message","processed_at":"2026-09-26T13:35:04.906Z","content":[{"type":"text","text":"OK"}]}}
```

| Field   | Meaning                                                                                                               |
| ------- | --------------------------------------------------------------------------------------------------------------------- |
| `type`  | `session.events.ready` (ready, sent once) or `session.event` (one event)                                              |
| `event` | Only on `session.event` frames; same shape as a single event from [List events](/en/docs/api/native/agent/listEvents) |

The same event can arrive more than once. A `user.message`, for example, first appears with `processed_at: null` (accepted, pending) and again with a timestamp once processed. Deduplicate by `event.id` and keep the latest.

## Close codes [#close-codes]

| Code   | Meaning                                     | What to do                                  |
| ------ | ------------------------------------------- | ------------------------------------------- |
| `1000` | The server ended the subscription normally  | Reconnect if you still need events          |
| `1008` | The client sent a message on the connection | The connection is read-only; send no frames |
| `1011` | Internal server interruption                | Reconnect and backfill with List events     |
| `1013` | A limit was exceeded for now (see below)    | Reconnect and backfill with List events     |

## Limits [#limits]

* One connection pushes at most about **512 KB** of events in total, then closes with `1013`. Be ready to reconnect on long sessions.
* The server retries its internal connection to the session automatically; if repeated retries fail, it also closes with `1013`.

## Disconnects and backfill [#disconnects-and-backfill]

Events produced while you are disconnected are **not replayed**. After reconnecting, fill the gap with [List events](/en/docs/api/native/agent/listEvents) by time:

```http
GET /agent/v1/sessions/{session_id}/events?order=asc&created_at[gt]=<processed_at of the last event you received>
```

Backfilled events and newly pushed ones can overlap; deduplicate by `event.id`.

## Examples [#examples]

Bun (the built-in `WebSocket` supports `headers`):

```ts
const ws = new WebSocket(
  `wss://api.deepraxis.com/agent/v1/sessions/${sessionId}/events/stream`,
  { headers: { Authorization: `Bearer ${process.env.PRAXIS_API_KEY}` } },
);
ws.addEventListener("message", (message) => {
  const frame = JSON.parse(String(message.data));
  if (frame.type === "session.events.ready") console.log("ready");
  if (frame.type === "session.event") console.log(frame.event.type, frame.event);
});
ws.addEventListener("close", (close) => console.log("closed", close.code));
```

Node (the `ws` package):

```ts
import WebSocket from "ws";

const ws = new WebSocket(
  `wss://api.deepraxis.com/agent/v1/sessions/${sessionId}/events/stream`,
  { headers: { Authorization: `Bearer ${process.env.PRAXIS_API_KEY}` } },
);
ws.on("message", (data) => {
  const frame = JSON.parse(String(data));
  if (frame.type === "session.event") console.log(frame.event.type, frame.event);
});
ws.on("close", (code) => console.log("closed", code));
```

Python (the `websockets` package):

```python
import asyncio, json, os
import websockets

async def main(session_id: str):
    url = f"wss://api.deepraxis.com/agent/v1/sessions/{session_id}/events/stream"
    headers = {"Authorization": f"Bearer {os.environ['PRAXIS_API_KEY']}"}
    async with websockets.connect(url, additional_headers=headers) as ws:
        async for message in ws:
            frame = json.loads(message)
            if frame["type"] == "session.event":
                print(frame["event"]["type"], frame["event"])

asyncio.run(main("sesn_123"))
```
