Session Recovery
Session Recovery
Recover WebSocket sessions after disconnection without losing subscriptions or missing events.
Overview
Session recovery is best effort — it increases resilience but is not guaranteed. On reconnect, the server attempts to route you to the same instance that held your session, but this may not always succeed (e.g. if the instance has reached its session limit). Design your client to handle both successful recovery and fallback to a fresh session.
- Client sends
enable_session_recovery(requires auth) — receivesrecovery_token+ttl_seconds - Connection drops — server detaches session, preserves subscriptions, buffers incoming events for 30 seconds
- Client opens a new connection and sends
recover_sessionas the very first message — do not sendauthfirst, the recovery request includes theapi_key - Server replays missed messages, restores subscriptions, returns a new token
recover_sessionreplaces the normal auth flow. If you sendauthbeforerecover_session, the connection is no longer pristine and recovery will fail.
If recovery fails (token expired, instance mismatch, buffer overflow), fall back to a fresh auth → subscribe flow.
The recovery window defaults to 30 seconds after disconnect. Buffered messages are capped at 1000.
enable_session_recovery
Enable recovery on the current authenticated session.
Rate limit: 1 request per 10 seconds.
Request
{
"type": "enable_session_recovery",
"id": "optional-correlation-id"
}| Field | Type | Required | Description |
|---|---|---|---|
type | "enable_session_recovery" | Yes | Message type |
id | string | number | No | Optional request correlation ID |
Response
{
"kind": "response",
"type": "enable_session_recovery",
"id": "optional-correlation-id",
"message_id": "550e8400-e29b-41d4-a716-446655440000",
"timestamp_ms": 1677721600000,
"success": true,
"recovery_token": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"ttl_seconds": 30
}| Field | Type | Description |
|---|---|---|
recovery_token | string | UUID token for future recovery. Idempotent — returns same token if already enabled. |
ttl_seconds | number | Seconds the recovery window lasts after disconnect |
Errors
PAYLOAD_VALIDATION_ERROR— invalid payloadAUTHENTICATION_FAILED— not authorized- Rate limit exceeded
recover_session
Restore a detached session on a new connection.
Rate limit: 3 requests per 10 seconds per IP.
Preconditions: recover_session must be the first message on a fresh connection — not authorized, no subscriptions, no prior messages (seq_id must be 0). Do not send auth first.
Request
{
"type": "recover_session",
"recovery_token": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"last_seq_id": 42,
"api_key": "your_api_key"
}| Field | Type | Required | Description |
|---|---|---|---|
type | "recover_session" | Yes | Message type |
id | string | number | No | Optional request correlation ID |
recovery_token | string | Yes | UUID token from enable_session_recovery |
last_seq_id | integer | Yes | Last seq_id successfully received by the client (>= 0) |
api_key | string | Yes | The same API key used in the original session |
Response
Before the success response, the server replays all missed messages (from last_seq_id + 1 onwards) in order, each with their original seq_id. Pending snapshots are also resent.
{
"kind": "response",
"type": "recover_session",
"id": "optional-correlation-id",
"message_id": "660e8400-e29b-41d4-a716-446655440001",
"timestamp_ms": 1677721600500,
"success": true,
"recovery_token": "new-token-for-next-recovery",
"recovered_subscriptions": [
{ "channel": "orderbook_options", "query": { "instrument_name": "BTC_USDC-31OCT25-130000-C" } },
{ "channel": "trade", "query": {} }
],
"last_seq_id": 55,
"buffered_messages_count": 13
}| Field | Type | Description |
|---|---|---|
recovery_token | string | New token for the recovered session (old token is consumed) |
recovered_subscriptions | Subscription[] | All subscriptions restored from the previous session |
last_seq_id | number | The seq_id of the recovery response itself |
buffered_messages_count | number | Number of messages replayed before this response |
Errors
| Error | Condition |
|---|---|
"Cannot recover session on an already authorized connection" | Connection is already authenticated |
"Cannot recover session on a connection with existing subscriptions" | Connection has active subscriptions |
"Cannot recover session on a connection that has already exchanged messages" | seq_id > 0 |
API_KEY_VERIFICATION_FAILED | Invalid or revoked API key |
"Recovery token not found or expired" | Token invalid, expired, or API key mismatch |
"Recovery token is already in use" | Another recovery attempt in-flight for this token |
"Client last_seq_id exceeds detached session state" | last_seq_id > server's detached state |
"Recovery gap expired from resend cache" | Missed messages no longer available |
PAYLOAD_VALIDATION_ERROR | Malformed request |
If replay fails mid-stream, the server closes the connection with WebSocket close code 1011.
Lifecycle Example
Client A Server
| |
|--- auth ---> |
|<--- auth success --- |
|--- enable_session_recovery ---> |
|<--- { recovery_token, ttl } --- |
|--- subscribe orderbook ---> |
|<--- snapshot --- |
|<--- event (seq_id: 10) --- |
|<--- event (seq_id: 11) --- |
| |
X connection drops |
| | (server detaches, buffers events)
| |<--- event (seq_id: 12, buffered)
| |<--- event (seq_id: 13, buffered)
| |
Client B (new connection, no auth) |
|--- recover_session (first msg) |
| { token, last_seq_id: 11 } -->|
| |
|<--- event (seq_id: 12, replay) --|
|<--- event (seq_id: 13, replay) --|
|<--- recover_session success --- |
| { new_token, last_seq_id: 14 }|
| |
|<--- event (seq_id: 15, live) --- |Updated 4 days ago
