September 14, 2026

How to Reconstruct Hyperliquid L4 Order Books Correctly

featured image

Level 4 order book data gives you something aggregated market depth cannot: the individual orders behind the book and the lifecycle events that change them.

But receiving L4 data is only half the problem.

For advanced market data systems, the harder task is reconstructing the correct state from snapshots, incremental updates, parent and child orders, conditional orders, cancellations, rejections, and reconciliation events.

CoinAPI's Hyperliquid book_l4 feed provides order-by-order snapshots and updates with exchange order IDs, price, size, order type, time-in-force, user attribution, Hyperliquid status information, and conditional-order structures.

To use that information correctly, an application needs more than a parser.

It needs a state machine.

At L2, the primary question is:

→ How much liquidity is available at each price?

L3 adds individual orders behind those price levels.

L4 adds more context around those orders and how they behave. CoinAPI's Hyperliquid book_l4 feed can expose fields including:

FieldPurpose
idExchange-provided order identifier
priceCurrent order price
sizeCurrent order size
userHyperliquid user/account associated with the order
cloidClient order identifier when provided
order_typeHyperliquid order type
orig_sizeOriginal order size
tifTime-in-force
reduce_onlyReduce-only state
update_typeLifecycle operation on incremental messages
hl4_statusHyperliquid-specific order status
is_triggerWhether the order is conditional
is_childWhether the record represents a child order
childrenNested contingent orders associated with a parent
trigger_pxTrigger price
trigger_conditionTrigger condition

The result is not simply a collection of (price, size) pairs. It is a stream describing market state and transitions between states.

That changes how the consumer should be designed.

A common implementation mistake is to process every incoming L4 record as an independent order-book row.

That model quickly becomes insufficient.

A record can represent a resting parent, a contingent TP/SL child, an order being removed, a rejection, or part of the transition between one valid market state and another.

A safer architecture separates three concepts:

Raw events → normalized order state → executable depth

Raw events preserve exactly what the feed delivered.

Normalized order state tracks relationships and lifecycle.

Executable depth contains only the orders that currently contribute liquidity to the market.

Keeping these layers separate prevents contingent or informational records from accidentally becoming tradable depth.

A message with:

is_snapshot = true

represents a full replacement state.

The consumer should clear its previous reconstructed state and rebuild from that snapshot. Subsequent messages are then applied according to the connection-local sequence.

The basic lifecycle is:

1FULL SNAPSHOT (authoritative baseline, any sequence)
23CLEAR PREVIOUS STATE
45BUILD CURRENT ORDER STATE
67NEXT MESSAGE
8      ├── full snapshot → replace again (valid even if sequence is not last+1)
9      └── incremental with sequence last+1 → apply

If the connection is restarted, resubscribe and rebuild from the next valid full snapshot. Sequence numbers are connection-local. They should not be treated as permanent global exchange sequence identifiers.

Heartbeats and other unnumbered control messages do not consume sequence numbers. A discarded, unsent delta does.

A full snapshot (is_snapshot=true) is self-consistent and authoritative regardless of its sequence number. If the next message is a full snapshot, replace state and continue even when the sequence is not last+1. At subscribe, snapshot sequence 2 followed by snapshot sequence 4 with no visible 3 is a known server race on a busy book, not client data loss and not a reason to invalidate. Reconnect will often reproduce it. Sequence 4 is a valid recovery boundary.

Resubscribe when an incremental is missing and the next message is not a full snapshot, or when the fully reconstructed generation is invalid (crossed, structurally incomplete, or otherwise unusable). A mid-stream incremental gap that is not followed by a full snapshot is still a recovery condition.

It is tempting to assume that a snapshot describes state at moment T, while the first incremental message contains only events strictly after T.

That assumption is too strong.

The first incremental update can overlap with state already represented by the snapshot. It can contain SET records for orders already present and DELETE records for orders already absent.

Reconstruction logic should therefore be idempotent:

1SETupsert
2DELETEdelete if present

A SET for an existing order does not automatically indicate duplication.

Likewise, a DELETE for an order that does not exist in the reconstructed state is a safe no-op rather than automatic proof that a preceding event was lost.

This behavior is not limited to the first delta. During a continuous sequence, an absent-ID DELETE alone does not require resynchronization.

Snapshot and incremental messages can share timestamp values.

That does not mean the incremental message should be ignored.

For state reconstruction, the authoritative controls are:

is_snapshot

and the connection-local:

sequence

CoinAPI does not provide an additional snapshot watermark or source batch identifier for distinguishing overlapping reconciliation content.

A consumer therefore should not implement logic such as:

1if delta.time_exchange == snapshot.time_exchange:
2    ignore(delta)

That can discard legitimate state changes.

time_exchange can also lag wall-clock time during a stall on the Hyperliquid node. Timestamp lag by itself does not indicate a sequence gap, crossed book, or invalid generation.

Use sequence and is_snapshot to control reconstruction. time_exchange remains the exchange event time, not proof that the feed is real-time.

In a conventional L3 implementation, an order map might look like:

1orders[id] = order

Hyperliquid L4 requires more care.

A parent order and a TP/SL child representation can share the same numeric order ID. Treating id as globally unique across every representation can therefore make valid structures look like duplicate orders.

At minimum, parent and child representations should be distinguished using:

1(id, is_child)

Structural placement matters too.

In snapshots:

1asks[] / bids[]
2      └── parent
3            └── children[]
4                  ├── TP
5                  └── SL

Top-level asks and bids contain parent-order representations. Nested children[] represent contingent children associated with those parents.

Flattening everything into one global ID map removes information the consumer needs to interpret the book correctly.

Consider a simplified structure:

1{
2  "id": "532800366621",
3  "price": 81197,
4  "size": 0.00118,
5  "order_type": "Limit",
6  "is_child": false,
7  "children": [
8    {
9      "id": "532800366621",
10      "order_type": "Take Profit Market",
11      "is_trigger": true,
12      "is_child": true
13    }
14  ]
15}

The repeated ID does not mean the book contains two independent resting orders.

The top-level Limit order contributes to the current book.

The nested Take Profit Market child describes contingent state.

A reconstruction engine should preserve that relationship rather than inserting both records independently into price-level depth.

For a full snapshot, the resting-liquidity test is based primarily on structural state.

A positive-size order represents current executable depth when it is:

  • top-level,
  • not a child,
  • and not currently a trigger.

In snapshot messages, update_type and hl4_status may be absent. That is expected.

Likewise:

is_child=false

is_child=null

and an omitted is_child

can all represent a non-child parent in this context.

A consumer therefore cannot require:

1update_type = set
2hl4_status = open

for every snapshot order.

Conditional orders create another subtle case.

An order labeled Stop Market, Stop Limit, or Take Profit Limit does not necessarily remain outside executable depth.

After a trigger fires, Hyperliquid can convert the order into executable resting liquidity while retaining its original order-type label.

As a result, a positive-size, top-level, non-child, non-trigger order can belong in executable depth even when its order_type still describes its conditional origin.

The safer rule is:

1top-level
2+ positive size
3+ non-child
4+ non-trigger
5= current resting liquidity

An optional trigger_condition="Triggered" can provide additional context about how the order reached that state, but it does not by itself exclude the order from depth.

The opposite rule applies to nested TP/SL children.

They can be important for understanding an order's future lifecycle, but they should not independently increase current bid or ask depth while they remain contingent.

Suppose the current state contains:

1Parent:
2BUY 1 BTC @ 75,000
3
4Children:
5Take Profit Market
6Stop Market

That does not mean three orders contribute liquidity.

The current book contains the resting parent. The children describe contingent instructions that may become relevant later.

This distinction is critical when calculating available liquidity, depth imbalance, queue structure, or execution simulations.

Parent/child handling becomes more complex when several contingent legs share the same placeholder ID.

CoinAPI preserves multiple delivered child representations, but (id, is_child) does not necessarily provide a durable identifier for every individual TP/SL leg.

At event time, fields including order_typetrigger_px, and trigger_condition can help correlate a terminal event with a particular child.

But these fields should not be treated as a guaranteed permanent identity key. Do not match child terminals on parent price or side. Those fields often describe the parent, not the leg.

For systems requiring deterministic per-leg lifecycle attribution:

Do not manufacture identity when the feed does not provide it.

If a particular child lifecycle cannot be resolved deterministically, mark that relationship as unresolved rather than guessing which leg changed.

If a terminal parent event does not re-emit its contingent children, do not assume that every stored child should be deleted automatically.

Retain a contingent child until its own explicit terminal event is received, either within a parent structure or through a separate child event.

A child can later become triggered, canceled, rejected, or otherwise terminated.

Until then, it remains lifecycle state rather than executable liquidity.

This separates the parent's current resting state from the lifecycle of contingent instructions associated with it.

The same order ID can legitimately appear more than once inside an ordered set of events.

For example:

1SET / OPEN
23DELETE / CANCELED

If a validator checks for duplicate IDs before interpreting the operations, it may reject a valid lifecycle sequence.

Instead, apply records in delivered order.

A simplified state machine looks like:

1SET / OPEN
2    → add or replace state
3
4REJECTED
5    → informational
6    → do not add to depth
7
8DELETE / terminal status
9remove state

Terminal states such as filled, canceled, triggered, and supported *_canceled statuses are represented through deletion semantics.

Only after the complete delivered message has been processed should the resulting order-book state be validated.

On incremental messages (is_snapshot=false), a top-level parent should carry update_type and hl4_status. Nested children[]is_child, and children_oids are snapshot-only fields.

If a top-level incremental parent has neither operation nor status, do not interpret its presence as an order-book operation.

The safe behavior is:

  • do not change the parent's resting-book state,
  • do not add nested children[] to bid or ask depth,
  • and do not infer a child SETPENDING, or other lifecycle transition from that object.

Preserve the raw record if needed for diagnostics or replay.

Child lifecycle should only be updated when a normal incremental event or a later full snapshot provides the required state.

The current Hyperliquid book_l4 incremental contract is based on setdelete, and rejected.

Residual add and sub can still appear. They are not anonymous price-level size adjustments, and they are not a generation break.

Until the residual encoder path is removed, apply them per id, in delivered order, with the same meaning as set and delete:

1set  or add  → this id's resting size is now X
2delete or sub → this id is gone

The same rule applies whether the id was in the snapshot or not, and whether the message is the first delta or a later incremental. An add immediately followed by a delete for the same id nets to zero.

Do not resubscribe only because add or sub appeared, and do not combine them as aggregate level deltas.

A production reconstruction engine should continuously test basic market invariants.

One obvious check for a normal non-crossed resting book is:

1best_bid < best_ask

But the point at which that check runs matters.

Do not reject the state because an intermediate operation inside an ordered message temporarily creates something unusual.

Instead:

1Receive message
23Apply record 1
45Apply record 2
67Apply record 3
89Apply complete message
1011Project executable depth
1213Validate resulting book

If the fully reconstructed snapshot or the state produced after applying an entire incremental message is crossed or structurally incomplete, discard that connection generation and resubscribe for a new full snapshot.

The replacement snapshot must itself pass validation. A snapshot that remains crossed is not a successful recovery and should not become the new baseline.

The same principle applies to structurally incomplete top-level parents, including records missing expected fields such as user or order_type.

A robust Hyperliquid L4 consumer should do more than maintain an in-memory dictionary of orders.

A useful architecture is:

1WebSocket
23Raw Journal
45Parser
67Semantic State Machine
89Normalized Order State
1011Executable Depth Projection
1213Invariant Validation
1415Research / Execution / Analytics

Each layer solves a different problem.

Persist the original provider message before semantic transformation.

This makes it possible to replay the exact stream when reconstruction logic changes or an unknown state appears.

Validate message structure and convert fields into internal types without deciding what they mean for the book.

Parsing and market-state interpretation should remain separate.

Apply snapshot, parent/child, SET, DELETE, REJECTED, conditional-order, and recovery rules.

This is where feed semantics become state transitions.

Maintain currently known orders and contingent relationships.

This representation should preserve more information than the final price-level book.

Project only current resting liquidity into:

1priceaggregate size

This representation can then be used for spread, depth, imbalance, slippage, and similar calculations.

Check sequence continuity, impossible quantities, crossed states, missing required fields, unknown lifecycle combinations, and other conditions relevant to the application.

Keeping these layers separate makes failures easier to diagnose.

If an incorrect depth value appears, you can determine whether the problem came from the raw feed, parsing, lifecycle interpretation, or final aggregation.

A new WebSocket connection should not silently inherit the state of an old one.

Connection generations should be explicit:

1Generation 41
2Snapshot
3Delta
4Delta
5Delta
6DISCONNECT
7
8Generation 42
9Snapshot
10Delta
11Delta
12...

The first valid full snapshot in Generation 42 becomes the new authoritative baseline.

This is especially important because sequence is scoped to the lifetime of the connection.

A research or production system that persists L4 data should therefore retain connection or generation context alongside sequence information rather than treating sequences from separate sessions as one global series.

Advanced market-data systems often choose conservative validation.

But there is an important difference between unknown semantics and known idempotent behavior.

These conditions should not automatically invalidate the book:

1SET for an existing order
2DELETE for an absent order
3parent and child sharing an ID
4multiple ordered events for one ID
5snapshot/delta overlap
6full snapshot whose sequence is not last+1
7connect-time snapshot 2 followed by snapshot 4
8residual ADD/SUB applied as SET/DELETE aliases
9unnumbered heartbeat

They have defined interpretations.

By contrast, off-contract operations or an invalid fully reconstructed state require recovery rather than guesswork.

The useful rule is:

→ Be strict about meaning, not merely about repetition.

An L4 system can encounter states that cannot safely be reduced to a simple add/update/delete model.

When that happens, avoid heuristics that silently make the data look clean.

Preserve:

  • the raw message,
  • connection generation,
  • sequence,
  • timestamps,
  • structural placement,
  • lifecycle fields,
  • and the reason the state could not be interpreted.

Then make the uncertainty explicit downstream.

For quantitative research, an UNKNOWN state that can be filtered is safer than a fabricated lifecycle that contaminates a backtest.

Once the state machine is correct, L4 becomes much more than a more detailed order book.

Researchers can study how individual orders persist, change, and disappear.

Market microstructure teams can examine order lifetime, cancellation behavior, post-only activity, liquidity persistence, conditional-order behavior, order-flow dynamics, participant-level activity, queue and depth changes, and rejection patterns.

CoinAPI's dedicated Hyperliquid infrastructure also provides complementary trade_l4, oracle-price, TWAP-status, miscellaneous node-event, and system-event feeds, allowing teams to combine order-book state with execution and exchange-native context.

On live hl_twap_statuses, the canonical timestamp field is order_timestamp. Flat-file/CSV export uses order_timestamp_ms for the same unix-millisecond value. trade_l4 attributes the maker by wallet, not by maker order id; do not reconstruct remaining size by joining trades to book orders on wallet and price.

The value of L4 therefore comes from more than seeing individual orders.

It comes from reconstructing their behavior correctly.

A schema tells you which fields exist.

It does not always tell you how those fields interact over time.

For Hyperliquid L4, production consumers need to understand snapshot replacement, connection-local sequencing, parent/child structures, contingent liquidity, idempotent updates, terminal events, validation, and recovery boundaries.

Once those rules are implemented explicitly, the feed becomes far more useful for serious market microstructure work.

CoinAPI provides real-time Hyperliquid L4 data through the dedicated WebSocket DS endpoint, including book_l4, trade_l4, oracle prices, TWAP statuses, and Hyperliquid event streams.

Explore the Hyperliquid L4 documentation or contact CoinAPI to discuss access for quantitative research, market making, and production market-data systems.

background

Stay up-to-date with the latest CoinApi News.

By subscribing to our newsletter, you accept our website terms and privacy policy.

Recent Articles