Streams guide

Replay & backfill

On Ultra, replayable event streams can start from a block within the last 6 hours. The server delivers retained messages in block order, then hands the subscription over to live delivery.

How replay works

Replay is a request parameter, not a separate API. On the swaps stream it is start_block on the request; on the StreamData family it is start_block inside the subscription. A value of 0 (or omitting it) means “from the head”; a positive retained height means “from there.”

  • Strict block order. Replayed messages arrive in the same order the chain produced them, then live messages continue the sequence. Your consumer needs no special backfill mode.
  • Filters apply to history too. A replayed subscription honors the same coin/wallet filters as a live one, so backfilling one wallet's fills does not mean replaying the whole tape.
  • Catch-up is as fast as you can read. Until you reach the head, messages arrive at read speed, not chain speed. Make ingestion backpressure-aware and expect a burst.
  • Deep backfills belong in the archive. Replay shines for closing gaps measured in minutes or hours. For older history, request a manual export from the historical archive after coverage is confirmed. Raw exports preserve the live event fields and financial decimal strings.

The backfill-then-live pattern

Production consumers should persist the last block height they fully processed. When the checkpoint remains inside the 6-hour retained window, subscribe from checkpoint + 1 to recover and continue live. Older gaps require a manual historical export.

# Backfill-then-live: one loop handles cold start, restart, and recovery.import timeimport grpcimport hyperliquid_swaps_pb2 as pbimport hyperliquid_swaps_pb2_grpc as rpc META = (("x-api-key", "YOUR_KEY"),) def run(stub):    backoff = 1    while True:        checkpoint = load_checkpoint()          # last fully-processed height        request = pb.SwapRequest(            coins=["BTC"],            start_block=checkpoint + 1,          # replay the gap, then go live        )        try:            for msg in stub.StreamSwaps(request, metadata=META):                process(msg)                     # idempotent on (block, tid)                save_checkpoint(msg.block_height)                backoff = 1                      # healthy again        except grpc.RpcError as err:            if err.code() == grpc.StatusCode.DATA_LOSS:                # If the checkpoint is still inside the 6-hour replay window,                # the next iteration requests the missed range.                continue            time.sleep(backoff)                  # transient: backoff and retry            backoff = min(backoff * 2, 30)

Two details make this loop safe. First, process() must be idempotent: replay starts at a block boundary, so a message you half-processed before a crash may arrive again; deduplicate on a natural key like (block_height, tid) for fills or oid + status for orders. Second, checkpoint only after durable processing, never on receipt.

DATA_LOSS: replay is the recovery path

The contract is deliberate: you are told you have a gap instead of getting one you can't see. Recovery is the loop above: reconnect and request replay from your checkpoint while it remains inside the 6-hour window. If DATA_LOSS recurs, your consumer is structurally too slow: tighten filters, split markets across subscriptions, or move processing off the receive thread. Reconnect strategy and monitoring guidance live in the operations guide.

Which streams replay

Ultra replay from a recent block within 6 hours is supported on:

  • Trades / Swaps: Every executed fill, with the trader's wallet, realized PnL, fees, and maker/taker role.
  • Order lifecycle: Every order-status event: open, filled, canceled, triggered, rejected.
  • Raw book diffs: Every order-book delta (new, update, modified, remove) for full reconstruction.
  • TWAP algos: Native TWAP order status and execution progress, as it fills.
  • Events: Funding payments, liquidations, deposits, withdrawals, and ledger updates.

Height-based replay does not apply to these specific live methods: L2 Order Book, L4 Order Book, Blocks, Dedicated BTC price. L2 and L4 begin with current state, BTC is a live ticker, and both QuickNode block methods are live-only. Ultra customers can replay blocks through the Dwellir-compatible StreamBlocks method usingPosition.block_height. To recover a book range from a checkpoint you already hold, combine that compatible state with ordered raw book diffs.

Related