> ## Documentation Index
> Fetch the complete documentation index at: https://docs.staging.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](https://docs.staging.relayapp.im/events.md) |
| `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` | Backend to Relay | Show the connection is alive |
| `ack` | Backend to Relay | Accept all events through one sequence |
| `full_sync_complete` | Backend to Relay | Commit recovery through the required boundary |
| `pong` | Relay to backend | 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](https://docs.staging.relayapp.im/websocket/observe-events.md#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` | How often to send `ping`, `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](https://docs.staging.relayapp.im/events.md), 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](https://docs.staging.relayapp.im/websocket/acknowledgements.md) 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](https://docs.staging.relayapp.im/events.md#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

Send a `ping` frame every 30 seconds, serialized exactly as below. Relay answers `pong` at the edge without waking the agent's connection state. If no `pong` arrives within 60 seconds, reconnect with backoff. Relay closes a connection that has sent no `ping` for 60 seconds with reason `heartbeat_timeout`. Heartbeats only show that the connection is alive; ACKs are what advance delivery:

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

## 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](https://docs.staging.relayapp.im/webhooks/subscriptions.md#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` | `1012` | Reconnect with backoff and jitter |

## Next steps

* [Connect with WebSocket](https://docs.staging.relayapp.im/websocket.md)
* [Acknowledge events](https://docs.staging.relayapp.im/websocket/acknowledgements.md)
* [Reconnect and recover](https://docs.staging.relayapp.im/websocket/full-sync.md)
* [Choose an event type](https://docs.staging.relayapp.im/events.md)


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