For the complete documentation index, see llms.txt. This page is also available as Markdown.

Session Management and Reconnection

Session persistence, reconnection, and automatic replay for reliable WebSocket connections

Sessions enable reliable WebSocket connections with automatic replay of missed data on reconnect.

Overview

When you connect to the WebSocket API, you receive a sessionId that persists your subscriptions and tracks your position in the data stream. On reconnect, provide this session ID to:

  1. Restore subscriptions - No need to re-subscribe

  2. Receive replay - Automatically get missed data (up to 30 seconds)

  3. Resume seamlessly - Continue from where you left off

Key Concepts

Sequence Numbers (seq)

Every live data message includes a seq field - a monotonically increasing integer for client use only:

{
  "type": "allFills",
  "channel": "allFills",
  "seq": 42,
  "fills": [...]
}

Purpose: Detect gaps within a single connection. If you receive seq: 5 then seq: 7, you know seq: 6 was lost.

Key behaviors:

  • Resets to 1 on every new connection (including reconnects)

  • Per-subscription channel (each subscription has its own sequence)

  • Client-side only - server doesn't track your last received seq

Cursor

The cursor is your position in the data stream. It is an opaque string: store the latest one you received and send it back unchanged when you reconnect. The server handles all parsing.

Every live message and every replay chunk carries a cursor naming the last event in that message, precise to the individual event, so resuming from it neither misses nor duplicates events. Live-only channels with no replay (bbo, l2Book, allActiveAssetCtx, setOracleUpdates) send the sentinel "0".

Client-side tracking (recommended):

  • Every live event message includes a cursor field

  • Save this cursor as a string after successfully processing each message

  • Provide your cursor on reconnect for accurate replay

Server-side tracking (fallback):

  • The server also tracks a cursor for your session

  • Persists across disconnects

  • Used as fallback if you don't provide your own cursor

Manual cursor provision: When subscribing, you can optionally provide a cursor to request replay from a specific point:

This is useful for:

  • Resuming from a known checkpoint after application restart

  • Requesting specific historical data within the cache window (30 seconds)

Message Types

Control Messages

Type
Direction
Description

connected

Server→Client

New session created

reconnected

Server→Client

Existing session resumed

subscriptionUpdate

Server→Client

Subscription confirmed

ping

Server→Client

Heartbeat (respond with pong)

pong

Client→Server

Heartbeat response

error

Server→Client

Error occurred

Data Messages (Live Events)

Live event messages have the client subscription type in both type and channel. The values are always equal:

Field
Type
Description

type

string

Subscription type (e.g., "allFills")

channel

string

Same value as type; either field can be used for routing

seq

number

Sequence number for gap detection (resets on reconnect)

cursor

string

Position cursor (opaque string, e.g. "500:1706123456789:3")

fills/updates/events/trades/liquidations/data

array

Event data (field name varies by subscription type)

Common data message types: allFills, userFills, userOrderUpdates, liquidationFills, l4BookUpdates, etc.

Replay Messages

Replay messages are distinct from live events - they have "type": "replay" and contain historical data:

Field
Type
Description

type

string

Always "replay"

channel

string

The subscription type (e.g., "allFills")

cursor

string

Per-chunk cursor derived from the last event in this chunk (same format as live cursor). Use this for reconnection.

replayTimeMs

string

Same as cursor. Deprecated — use cursor instead.

count

number

Number of items in this chunk

chunk

number

Current chunk number (1-indexed)

totalChunks

number

Total chunks in this replay

hasMore

boolean

Always present. true while more chunks follow; false on the terminal chunk. Prefer it over chunk == totalChunks to detect the end of a replay.

hasGap

boolean

Only present when true. Set on the first chunk when the cache didn't cover the entire disconnect period, and repeated on the terminal chunk when the replay had to stop early. Absent when there is no gap.

data

array

Array of missed events (same format as live data)

Chunking

Large replays are split into multiple chunks to keep message sizes manageable. Chunking occurs when either limit is reached:

  • Max items: 2,000 items per chunk

  • Max size: 1 MB per chunk

Use hasMore (or chunk and totalChunks) to track replay progress:

  • Update your cursor from each chunk's cursor as it arrives — this ensures progress is saved even if the connection drops mid-replay

  • hasMore: true means another chunk follows; the terminal chunk has hasMore: false and chunk == totalChunks

  • hasGap is set on the first chunk (chunk: 1), and repeated on the terminal chunk if the replay stopped early

  • Process chunks in order as they arrive

Connection Flow

Initial Connection

Server responds with connected:

Save the sessionId - you'll need it for reconnection.

Subscribing

Server confirms with subscriptionUpdate:

requestId is optional, but a unique value is recommended for every subscribe or unsubscribe request. When present it must be at most 128 characters and must not contain control characters; invalid values are rejected with code: "invalid_request_id". The server echoes valid ids on both confirmations and errors, allowing multiplexed clients to match feedback to the request that caused it. Unsubscribe confirmations additionally contain unsubscribed. These correlation fields apply to the request/response exchange only; live data frames do not carry requestId.

Then live data begins flowing with seq starting at 1.

Reconnection Flow

Step-by-Step Process

  1. Client disconnects (network issue, restart, etc.)

  2. Server marks session disconnected - starts 30-second grace period

  3. Client reconnects with sessionId and cursor:

  4. Server validates session - checks if still within grace period

  5. Server automatically restores subscriptions - no need to re-subscribe

  6. Server sends reconnected:

  7. Server sends replay - events since your cursor (up to 30 seconds of data)

  8. Live events resume - with seq starting at 1

If your original connection used liveFormat=chunked-v1, you must include liveFormat=chunked-v1 again on reconnect. liveFormat is connection-level and is not persisted on the session.

Providing Your Cursor on Reconnect

When reconnecting, you can provide your last successfully processed cursor as a query parameter:

If you are using live chunking, include liveFormat=chunked-v1 as well:

Parameter
Required
Description

token

Yes

Your API key

sessionId

No

Session ID to resume (omit for new session)

cursor

No

Last cursor you received (opaque string, e.g. "500:1706123456789:3")

liveFormat

No

Required on every reconnect if you are using chunked-v1 live message format

Cursor priority:

  1. Client-provided cursor (recommended) - from the cursor query parameter

  2. Server-stored cursor (fallback) - used if you don't provide one

Note: lastSeq is always 0 because sequences reset on each connection.

What Triggers Replay

Replay is sent when ALL of these conditions are met:

  • You reconnect with a valid, non-expired sessionId

  • The session had active subscriptions before disconnect

  • There is cached data newer than your cursor

  • The cache TTL (30 seconds) hasn't fully expired

When Replay is NOT Sent

  • New connection (no sessionId provided)

  • Expired session (disconnected > 30 seconds ago)

  • No cached data for your subscriptions (quiet market)

  • Your cursor is already current (instant reconnect)

Gap Detection

The hasGap field in replay messages indicates data completeness:

hasGap

Meaning

Action

absent

Complete replay - no data lost

Process normally

true

Some data may be missing

Consider fetching from REST API

A gap occurs when your cursor is older than the replay coverage retained by the server - some events aged out before you reconnected. hasGap is determined from block coverage, not from individual events, so an item-time ordering violation and hasGap are separate signals and should be diagnosed independently.

Replay Response Scenarios

When you reconnect with a session, you'll receive one of these replay response types depending on market activity and your disconnect duration:

Scenario

count

hasGap

data

Meaning

Data, no gap

> 0

absent

Events array

Normal replay - you have all missed events

Data, with gap

> 0

true

Events array

Partial replay - some older events expired from cache

No data, with gap

0

true

[]

Cache empty and cursor is old - all events expired

No data, no gap

0

absent

[]

Quiet market - no events occurred during disconnect

Example Responses

Data with no gap (complete replay):

Note: hasGap is absent here because there is no gap. The field is only included when true.

Data with gap (partial replay - some events expired):

No data with gap (all cached events expired):

No data, no gap (quiet market - nothing happened):

Quiet Markets: A replay with count: 0 and hasGap: false confirms you're fully up-to-date - there simply were no events during your disconnect period. This is common for user-specific subscriptions (e.g., userFills) where activity is infrequent.

Message Ordering

The server holds live frames on a resumed connection until replay and restored snapshots finish. Client-side buffering remains safe. A live batch that straddles the last replayed item can still overlap replay; deduplicate as below.

Why Overlap Can Still Happen

The server adds you to live broadcast immediately, then holds those frames until recovery completes. Held live that the recovery watermark already covers is dropped. A live batch that straddles the last replayed item is delivered whole, so earlier items in that batch can duplicate replay.

allMids is snapshot-only and does not advance the shared session cursor. Per-reconnecting-client memory is bounded at the client channel plus the hold queue (2× WS_CLIENT_BUFFER_SIZE).

Deduplication

Why Overlap Occurs

To ensure no events are lost, the server:

  1. Subscribes you to live broadcasts immediately on reconnect

  2. Generates and sends replay data in parallel

This means events occurring during the brief window between these steps may appear in both streams. This is intentional - it's better to receive a duplicate than to miss an event.

Deduplication Keys

Each event type has a monotonically increasing key composed of (time, txIndex) (or similar). Since these are ordered, you only need to track the last seen key and skip any events at or before it.

Subscription Type
Dedup Key
Fields

allFills, userFills, builderFills, liquidationFills

(time, txIndex)

fill.time, fill.txIndex

userOrderUpdates, builderOrderUpdates

(time, txIndex)

update.time, update.txIndex

allTwapStatusUpdates

(time, txIndex)

update.time, update.txIndex

builderLiquidations, allBuilderLiquidations

(time, txIndex)

fill.time, fill.txIndex

allCompletedTrades, userCompletedTrades, builderCompletedTrades

(closeTime, txIndex)

trade.closeTime, trade.txIndex

userNonFundingLedgerEvents, allUserNonFundingLedgerEvents

(time, txIndex, role)

event.time, event.txIndex, user role

builderApprovedFundings

(time, user, coin)

entry.time, entry.user, entry.delta.coin

builderApprovedNonFundingLedgerEvents

(time, txIndex, user, role)

entry.time, entry.txIndex, entry.user, entry.role

Deduplication Example (Fills)

Since keys are monotonically increasing, track the last processed (time, txIndex) and skip events at or before it:

No Memory Cleanup Needed: Since you only track the last seen key (not a set of all seen IDs), there's no memory growth over time.

Session Lifetime

Parameter
Value
Description

Grace period

30 seconds

Time to reconnect before session expires

Redis TTL

60 seconds

Total time session persists in Redis (grace + buffer)

Replay cache

30 seconds

Maximum lookback for replay data

Cross-instance

Supported

Sessions persist across server restarts

Timeline Example

Examples

Best Practices

  1. Always save the sessionId from connected messages

  2. Always pass sessionId when reconnecting

  3. Track the cursor from each successfully processed message

  4. Always pass your cursor when reconnecting for accurate replay

  5. Distinguish replay from live - check msg.type === 'replay'

  6. Buffer live messages during replay processing

  7. Deduplicate events - replay and live may overlap (see Deduplication)

  8. Handle hasGap: true by fetching missing data from REST API if critical

  9. Track sequences within a connection to detect gaps

  10. Implement exponential backoff for reconnection attempts

  11. Respond to pings within 150 seconds to keep connection alive

Error Handling

Session Expired

If your session expired, you'll receive connected instead of reconnected:

This means:

  • A new session was created

  • You must re-subscribe to your feeds

  • No replay will be sent

Detection: Check msg.type === 'reconnected' to confirm successful resume.

Common Errors

Error
Cause
Solution

"Session not found"

Invalid or expired sessionId

Create new session, re-subscribe

"Invalid API key"

Bad token

Check API key

"Rate limit exceeded"

Too many connections/messages

Implement backoff

"Connection timeout"

Missed ping/pong

Respond to pings within 150s

Summary

Concept
Purpose
Client Responsibility

sessionId

Persist subscriptions across reconnects

Save and reuse

seq

Detect gaps within a connection

Track and validate

cursor

Stream position (opaque string)

Save from each message and replay chunk, provide on reconnect

Replay

Historical data on reconnect

Buffer live until complete

Deduplication

Replay/live events may overlap

Track (time, txIndex), skip events at or before it

hasGap

Only present when true - incomplete replay

Fetch from REST if critical

Auto-restore

Subscriptions restored automatically

None - just reconnect with sessionId

Remember: The server's cursor tracks what was sent, not what you received. For guaranteed data continuity, always track and provide your own cursor based on messages you successfully processed.

Last updated