> ## 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.

# WebSocket frames

> Read JSON frame shapes, sequence fields, heartbeats, errors, and disconnect codes.

Relay sends six JSON text frames after the upgrade, and your backend sends three back.

| Frame                | Direction        | Purpose                                       |
| -------------------- | ---------------- | --------------------------------------------- |
| `ready`              | Relay to backend | Confirm the connection and current checkpoint |
| `event`              | Relay to backend | Deliver one sequenced [event](/events)        |
| `full_sync`          | Relay to backend | Pause delivery and set the recovery boundary  |
| `error`              | Relay to backend | Report a protocol or delivery failure         |
| `disconnect`         | Relay to backend | State why Relay is closing the connection     |
| `ping`               | Relay to backend | Check connection liveness                     |
| `ack`                | Backend to Relay | Accept all events through one sequence        |
| `full_sync_complete` | Backend to Relay | Commit recovery through the required boundary |
| `pong`               | Backend to Relay | Answer a heartbeat                            |

## Read the ready frame

The first frame after the upgrade is `ready`. Read `acked_through` for the agent's durable checkpoint and `full_sync_required` to learn whether delivery is paused for recovery. An observer connection carries one extra field, shown on [observe events](/websocket/observe-events#verify-observer-readiness).

| Field                   | Meaning                                              |
| ----------------------- | ---------------------------------------------------- |
| `connection_id`         | ID for this connection                               |
| `acked_through`         | Highest sequence Relay has durably accepted          |
| `full_sync_required`    | Whether normal event delivery is paused for recovery |
| `full_sync_through`     | Exact recovery boundary, or `null`                   |
| `heartbeat_interval_ms` | Server ping interval, `30000`                        |
| `max_in_flight`         | Maximum unacknowledged events on this connection     |

## Read the event frame

Each `event` frame carries one sequence number and one [event envelope](/events), the same envelope a webhook delivers. Sequences are decimal strings scoped to one agent, sent oldest first. A replay after an uncertain close can repeat an `event_id` under a new sequence, so read [acknowledgements](/websocket/acknowledgements) before you accept anything:

```json captured-output theme={null}
{
  "type": "event",
  "sequence": "24",
  "event": {
    "api_version": "v1",
    "webhook_version": "2026-08-30",
    "event_type": "message.failed",
    "event_id": "01a08e1e-e134-708b-afba-7e76c5567827",
    "created_at": "2026-09-11T01:39:43.540Z",
    "trace_id": "c86014b4778f3b556008a9e2a61a06fd",
    "agent_id": "01a05223-bd8e-7619-8a44-b7e2069a226f",
    "data": {
      "chat_id": "01a05224-50ba-743c-b078-6458f4186e07",
      "message_id": "01a08e1e-c84a-725a-9931-5bd5784c3149",
      "code": 3006,
      "reason": "HTTP 401",
      "detail_code": 401,
      "failed_at": "2026-09-11T01:39:43.043Z"
    }
  }
}
```

Route on `event.event_type` and open its page in the [event catalog](/events#choose-an-event-type) for the `data` shape.

## Handle backpressure

Relay pauses delivery when the connection holds `max_in_flight` unacknowledged events, and your cumulative ACK opens the next window. A backend that stops acknowledging stops receiving, which is how you slow Relay down when your inbox is busy.

## Handle heartbeats

Relay sends a `ping` frame every 30 seconds. Answer within 60 seconds or Relay closes the connection with reason `heartbeat_timeout`. Heartbeats only show that the connection is alive; ACKs are what advance delivery:

```json theme={null}
{"type":"pong"}
```

## When it fails

An `error` frame names the problem in `code`. A fatal error ends consumption on that connection: stop using it, and let `retryable` decide whether to reconnect with backoff or fix the named condition first. A protocol mistake needs a corrected frame, not a retry of the same one.

| Code                 | Fatal | Action                                                      |
| -------------------- | ----- | ----------------------------------------------------------- |
| `invalid_frame`      | No    | Correct the client frame                                    |
| `ack_out_of_range`   | No    | Correct the cumulative ACK                                  |
| `stale_connection`   | Yes   | Stop the connection and restore valid authentication        |
| `ack_failed`         | Yes   | Reconnect with backoff and deduplicate any replay           |
| `delivery_failed`    | Yes   | Reconnect with backoff; Relay could not load the next event |
| `full_sync_required` | No    | Complete recovery before sending an ACK                     |
| `full_sync_mismatch` | No    | Send the exact required recovery boundary                   |

A `disconnect` frame arrives before Relay closes the socket, and the close code repeats the reason. A `4410` means the agent now has a webhook subscription; read [choose the delivery path](/webhooks/subscriptions#choose-the-delivery-path) before you reconnect.

| Reason               | Close code | Action                                            |
| -------------------- | ---------- | ------------------------------------------------- |
| `webhook_configured` | `4410`     | Delete every webhook subscription, then reconnect |
| `revoked`            | `4401`     | Stop, restore a valid Agent Token, then reconnect |
| `heartbeat_timeout`  | `4408`     | Reconnect with backoff                            |
| `restart`            | `1011`     | Reconnect with backoff and jitter                 |

## Next steps

* [Connect with WebSocket](/websocket)
* [Acknowledge events](/websocket/acknowledgements)
* [Reconnect and recover](/websocket/full-sync)
* [Choose an event type](/events)
