# Realtime

Shared Centrifugo client, Messages() delivery, handshake-before-return, and fail-closed overflow.

`client.Realtime` is the shared Centrifugo WebSocket client. Prefer typed service helpers (`client.Orders.Subscribe`, `client.Balances.Subscribe`, …). They return `*realtime.Subscription[T]`.

Realtime is binary-only: the transport negotiates `centrifuge-protobuf` and uses length-delimited Protobuf control frames and publication payloads. JSON wire mode applies only to ConnectRPC debugging, never to realtime. Incoming WebSocket messages and protobuf record/field lengths are capped at 8 MiB.

Managed order-book and market-overview subscriptions return dedicated types that expose **`Updates()`** instead of `Messages()`. They also expose `SetOnError(...)` and `Err()`; register the callback so a managed feed failure cannot leave quoting logic on a frozen book.

## Subscription contract

| API                  | Behavior                                                            |
| -------------------- | ------------------------------------------------------------------- |
| `Messages()`         | `<-chan T` of decoded publications; closes when the sub ends        |
| `Done()`             | Signal when the subscription has fully stopped                      |
| `Err()`              | Terminal error (`*errors.QueueOverflowError` on overflow)           |
| `SetOnError`         | Callback for background transport/feed errors                       |
| `Resubscribes()`     | Count of successful reconnect subscriptions; continuity may be lost |
| `TakeResubscribed()` | Edge-triggered reconnect-gap signal for reconciliation              |
| `Close()`            | Stop the subscription                                               |

**There is no `Recv()` method in Go.** Range or select on `Messages()` (or `Updates()` for managed books).

```go
sub, err := client.Orders.Subscribe(ctx, accountID)
if err != nil { log.Fatal(err) } // handshake already completed
defer sub.Close()
sub.SetOnError(func(err error) {
    log.Printf("realtime interruption: %v", err)
})

for order := range sub.Messages() {
    fmt.Println(order.Status, order.OrderID)
}
if err := sub.Err(); err != nil {
    log.Printf("ended: %v", err)
}
```

## Handshake before return

`SubscribeProto` / service subscribe helpers wait for the Centrifugo connect/subscribe handshake (including private token fetch) before returning. Initial auth failures return an error immediately; they do not reconnect forever in the background. Reconnects after a successful handshake use capped exponential backoff with per-subscription jitter. A successful resubscription resets the backoff.

## Private channels need Account ID

Private channels require API-key credentials and an Account ID (`Subscribe(ctx, accountID)` or `Config.DefaultAccountID` / `POLYESTER_ACCOUNT_ID`).

## Fail-closed overflow

> **No silent drops**
>
> Bounded queues. A slow consumer fails the subscription with `*errors.QueueOverflowError` instead of dropping updates.

Generic typed subscriptions default to a 1000-item queue. Managed order-book subscriptions use a dedicated 200-item update queue. Either queue fails closed on overflow.

## Snapshot-then-stream

Order book and market overview managed subscriptions snapshot via REST, then stream deltas/batches with gap recovery. Their create methods wait for the WebSocket handshake and initial snapshot before returning. Prefer those helpers over wiring raw channels yourself.

## Lower-level

```go
sub, err := realtime.SubscribeProto(ctx, client.Realtime, channel, decodeFn)
```

See [Streaming](https://testnet.polyester.com/docs/sdk/go/guides/streaming). Session model: [WebSocket session model](https://testnet.polyester.com/docs/developer-docs/shared-concepts/websocket-session-model).
