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",
  "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 tracks your position in the data stream, tied to Hyperliquid's block production. The server supports two cursor formats:

Cursor Format

Cursors use a colon-separated format with up to 3 components:

Format
Example
Description

block:timestamp:tx_index

"500:1706123456789:3"

Full precision — most subscription types

block:timestamp

"500:1706123456789"

Block-level — used by l4BookUpdates, fundingRates, and setOracleUpdates

timestamp (number)

1706123456789

Numeric — used by leverage and isolated margin update subscriptions

The tx_index component enables precise sub-block positioning. Within a single block (~70ms), there can be hundreds of events. Without tx_index, resuming from a cursor could miss events or produce duplicates when boundaries fall mid-block.

Treat cursors as opaque strings — always store and return the full cursor exactly as received. The server handles all parsing.

Cursor format: The server always sends cursors in block:timestamp:tx_index format (or block:timestamp for block-level subscriptions). Clients should treat cursors as opaque strings.

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 subscription type as their type field:

Field
Type
Description

type

string

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

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

hasGap

boolean

Only present when true. Indicates cache didn't cover entire disconnect period (only meaningful on first chunk). 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 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

  • hasGap is only set on the first chunk (chunk: 1)

  • 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:

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 oldest cached event - some events aged out before you reconnected.

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

Why This Happens

For minimal latency, the server:

  1. Adds you to live broadcast immediately on reconnect

  2. Sends replay data in parallel

In high-throughput scenarios, a live event may arrive before the final replay chunk.

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

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 (block:ts:tx_index)

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