Skip to content
Alchemy Logo

Replay and recovery

Beta

HyperCore APIs are in beta. Message fields, limits, and behavior may change, including breaking changes. To request access, contact [email protected].

How you recover after a disconnect depends on the kind of stream.

Stream kindStreamsRecovery
EventallFills, userFills, builderFills, liquidationFillsResubscribe with fromBlock to replay recent blocks.
Bookl2Book, l2BookDiff, bbo, allMids, l4BookUpdatesResubscribe. The first message is a fresh snapshot (isSnapshot: true) that replaces your local state.

Fill streams accept an optional fromBlock in the subscription. Delivery starts at that block (inclusive) and continues live. Omit fromBlock to receive live data only.

{"method":"subscribe","subscriptionId":"fills","subscription":{"type":"allFills","coins":["BTC","ETH"],"fromBlock":1183765139}}

To resume without loss:

  1. After processing each data message, persist data.blockNumber.
  2. On reconnect, subscribe with fromBlock set to the last processed block plus one.
  3. Deduplicate on the fill's trade ID and user (tid + user). Both sides of a trade share one tid, so tid alone isn't unique.

Recent blocks are held for replay for about 60 seconds. Reconnect within that window to resume without loss. The window isn't guaranteed and can be shorter during heavy activity.

If fromBlock is older than the available history, you receive a gap message for the range that can't be replayed, followed by data from the oldest block still available:

{"seq":1,"channel":"allFills","subscriptionId":"fills","gap":{"fromBlock":1183755197,"toBlock":1183759257}}

The range in gap is inclusive. Fills in that range aren't available from the stream; record the gap and continue. A gap can also arrive in other cases: when the service itself missed blocks, and occasionally when the server is under heavy load and skips a replay it could otherwise serve. Handle every gap the same way.

A fromBlock ahead of the latest block isn't rejected and doesn't wait for that block: the stream starts at the live tip, so you receive blocks lower than the fromBlock you sent. Only send block numbers you have already seen.

Book streams don't support fromBlock; if you send it, it's ignored. Every new subscription begins with a snapshot (isSnapshot: true) of current state, so after a reconnect you resubscribe and replace your local state with the snapshot. Changes that happened while you were disconnected aren't replayed; the snapshot reflects their result.

Occasionally, while your subscription stays open, the service re-seeds a book (for example after an internal data gap). You then receive a book_reset error on that subscription, followed by a new snapshot. Drop your local book for that subscription and rebuild it from the snapshot. The book_reset message carries the next seq, so it doesn't look like a gap.

Track seq on every subscription. A skipped seq means a message was lost:

  • Book stream: resubscribe (or reconnect) and rebuild from the new snapshot.
  • Fill stream: resubscribe with fromBlock set to the last block you fully processed plus one.

  • Use exponential backoff with jitter. Many clients reconnecting at the same moment can hit connection limits.
  • Use fresh subscription IDs or the same ones; IDs only need to be unique within a connection.
  • Deployments close connections with 1001, without a gap or book_reset. Reconnecting and resubscribing is the recovery path.
Was this page helpful?