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

# Connect with WebSocket

> Open an authenticated WebSocket consumer for an always-on agent backend.

Connect your backend to Relay over WebSocket in two steps: confirm the agent has no webhook subscription, then open the connection.

## Before you start

* An Agent Token, held on trusted server infrastructure.
* A durable write that [accepts an event](/websocket/acknowledgements) under a unique `event_id`.
* A durable write that [replaces state from a REST snapshot](/websocket/full-sync) when Relay asks for recovery.

## List webhook subscriptions

WebSocket delivery requires zero saved webhook subscriptions, because an agent uses one delivery path at a time. Read the list first; any saved subscription, active or not, makes the upgrade answer `409`:

<CodeGroup>
  ```bash cURL theme={null}
  curl -sS -A "relay-docs/1.0" https://api.relayapp.im/v1/webhook-subscriptions \
    -H "Authorization: Bearer $RELAY_AGENT_TOKEN"
  ```

  ```typescript TypeScript SDK theme={null}
  const { subscriptions } = await relay.webhookSubscriptions.list();

  if (subscriptions.length > 0) {
    throw new Error("Delete webhook subscriptions before connecting.");
  }
  ```
</CodeGroup>

An empty list means the agent is ready. To move an agent from webhooks to WebSocket, read [choose the delivery path](/webhooks/subscriptions#choose-the-delivery-path):

```json captured-output theme={null}
{"subscriptions":[]}
```

## Connect

The SDK manages heartbeats, reconnects with backoff, and acknowledges only after your callbacks resolve. `durableInbox` is your database and `readRelaySnapshot` is your complete REST reader. With a raw client, connect to `wss://api.relayapp.im/v1/websocket` with the token in the `Authorization` header:

<CodeGroup>
  ```bash cURL theme={null}
  # Inspect the upgrade response; use a WebSocket client for ongoing frames.
  curl -sS --http1.1 -i --no-buffer \
    'https://api.relayapp.im/v1/websocket' \
    -H "Authorization: Bearer $RELAY_AGENT_TOKEN" \
    -H 'Connection: Upgrade' -H 'Upgrade: websocket' \
    -H 'Sec-WebSocket-Version: 13' \
    -H "Sec-WebSocket-Key: $(openssl rand -base64 16)"
  ```

  ```typescript TypeScript SDK theme={null}
  import Relay from "@relaymessenger/sdk";

  const relay = new Relay({
    apiKey: process.env.RELAY_AGENT_TOKEN!,
    baseURL: "https://api.relayapp.im",
  });

  await relay.websocket.run({
    async onEvent(event) {
      await durableInbox.insertOnce(event.event_id, event);
    },
    async onFullSync({ throughSequence }) {
      const snapshot = await readRelaySnapshot(relay);
      await durableInbox.replaceSnapshot(throughSequence, snapshot);
    },
    onError(error) {
      console.error(error);
    },
  });
  ```

  ```http Raw WebSocket upgrade theme={null}
  GET /v1/websocket HTTP/1.1
  Host: api.relayapp.im
  Authorization: Bearer $RELAY_AGENT_TOKEN
  Connection: Upgrade
  Upgrade: websocket
  Sec-WebSocket-Version: 13
  Sec-WebSocket-Key: <generated by your WebSocket client>
  ```
</CodeGroup>

Redact the `Authorization` header from logs. Keep model work, tools, and replies in a separate worker that reads from the inbox; the `onEvent` callback only stores.

## What you get back

The upgrade answers `101`, and the first frame is [`ready`](/websocket/protocol#read-the-ready-frame) with the agent's checkpoint. Relay then sends `event` frames oldest first, up to `max_in_flight` unacknowledged at a time:

```http captured-output theme={null}
HTTP/1.1 101 Switching Protocols
Connection: upgrade
Upgrade: websocket
```

## When it fails

A `400` means the query string is wrong: send none, or exactly `observe=true`. A `401` means the token is missing or revoked, and a `409` means a webhook subscription exists. If the connection closes after the upgrade, read the [disconnect reasons](/websocket/protocol#when-it-fails) and the [recovery procedure](/websocket/full-sync).

## Next steps

* [Read the frames](/websocket/protocol)
* [Acknowledge events](/websocket/acknowledgements)
* [Reconnect and recover](/websocket/full-sync)
* [Observe events](/websocket/observe-events)
