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.