# l4BookUpdates stream

> HyperCore private-preview documentation.

> For the complete documentation index, see [llms.txt](/docs/llms.txt).

<Callout intent="info">
  This API is in private preview. Target September 2026; timing is subject to change.
</Callout>


# l4BookUpdates stream

`l4BookUpdates` delivers every individual order placed, resized, or removed,
including its position in the price-level queue. This is ideal for market making,
queue-position analysis, liquidity attribution, and reconstructing an order-level book.

For the corresponding gRPC update stream, see [StreamL4BookUpdates](/docs/chains/hypercore-grpc/api-reference/stream-l4-book-updates).

## Subscribe

```json
{
  "method": "subscribe",
  "subscription": { "type": "l4BookUpdates", "coins": ["BTC", "ETH"] }
}
```

Use `marketTypes` instead of `coins` to subscribe to all markets of selected types:

```json
{
  "method": "subscribe",
  "subscription": { "type": "l4BookUpdates", "marketTypes": ["spot"] }
}
```

## Subscription parameters

| Parameter     | Type     | Required | Source            | Description                                                                                                                                                                                                              |
| ------------- | -------- | -------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `coins`       | string[] | No       | Alchemy extension | Markets to subscribe to. Omit to subscribe to every market of the types in `marketTypes`, which defaults to perpetuals. The native API takes a single `coin` per subscription; the plural array is an Alchemy extension. |
| `marketTypes` | string[] | No       | Alchemy extension | Restricts delivery by market type. Accepted values: `perp`, `spot`, `outcome`, or `*`. Defaults to `["perp"]`. Rejected if combined with `coins`.                                                                        |

The default never grows. New market types must be added to `marketTypes` explicitly, or pass `["*"]` to opt in to future types automatically.

## Acknowledgement

```json
{
  "channel": "subscriptionResponse",
  "data": { "subscriptionId": "sub_01", "type": "l4BookUpdates" }
}
```

## Event envelope

```json
{
  "channel": "l4BookUpdates",
  "subscriptionId": "sub_01",
  "blockHeight": 123456,
  "blockTime": 1780000000000,
  "cursor": "<opaque cursor>",
  "data": {
    "height": 123456,
    "timestamp": 1780000000000,
    "isSnapshot": false,
    "diffs": [
      {
        "type": "new",
        "coin": "BTC",
        "oid": 12345,
        "user": "0x1111111111111111111111111111111111111111",
        "side": "B",
        "px": "100000.0",
        "sz": "0.10",
        "insertBefore": 12346
      }
    ]
  }
}
```

## Payload fields

| Field                  | Type    | Description                                                                                                    |
| ---------------------- | ------- | -------------------------------------------------------------------------------------------------------------- |
| `height`               | integer | Block height.                                                                                                  |
| `timestamp`            | integer | Block timestamp in milliseconds.                                                                               |
| `isSnapshot`           | boolean | `true` when the message contains a complete replacement snapshot; absent or `false` for an incremental update. |
| `diffs`                | array   | Per-order changes in this block.                                                                               |
| `diffs[].type`         | string  | `new`, `update`, or `remove`.                                                                                  |
| `diffs[].coin`         | string  | Market symbol.                                                                                                 |
| `diffs[].oid`          | integer | Order ID, unique and stable for the life of the order.                                                         |
| `diffs[].user`         | string  | Address that placed the order.                                                                                 |
| `diffs[].side`         | string  | `B` for bid, `A` for ask.                                                                                      |
| `diffs[].px`           | string  | Limit price, as a decimal string.                                                                              |
| `diffs[].sz`           | string  | Initial order size. Present on `new` only.                                                                      |
| `diffs[].origSz`       | string  | Order size before an `update`. Present on `update` only.                                                        |
| `diffs[].newSz`        | string  | Order size after an `update`. Present on `update` only.                                                         |
| `diffs[].insertBefore` | integer | Optional, on `new` only. Queue placement — see below.                                                          |

## Queue position

`insertBefore` places this order immediately ahead of the resting order with that ID at the same price level, reflecting HyperCore's priority placement rules. When absent, append the order to the tail of the queue. If the named order is no longer at that level, append to the tail.

## Update behavior

* `update` means the order's size changed, typically a partial fill; it carries `origSz` and `newSz`
* `remove` is terminal — the order was filled or cancelled and carries no operation-specific payload
* Grouping these orders by price produces the corresponding level-aggregated view — sum the sizes and count the orders at each price — so one subscription can serve both order-level and level-aggregated needs. This corresponds to an unaggregated `l2Book`; it does not reproduce a book requested with `nSigFigs` or `mantissa` applied.

## Bootstrap

The first message after subscribing carries a full order-book snapshot with `isSnapshot: true`, delivering every resting order as a `new` diff. Install it as local state, then apply later diffs. A later message with `isSnapshot: true` uses the same replacement mechanism for recovery.

For a snapshot shared by many consumers, or to let a slow client digest a large book while buffering live updates, use [the REST snapshot read](/docs/data/hypercore/rest-api/market-data-and-snapshots/l-4-book-snapshots) instead.

## Choosing a book stream

|                  | l2Book                       | l2BookDiff                | bbo                       | l4BookUpdates                          |
| ---------------- | ---------------------------- | ------------------------- | ------------------------- | -------------------------------------- |
| **Delivers**     | Complete aggregated snapshot | Changed aggregated levels | Best bid and ask only     | Changed individual orders              |
| **Client state** | None required                | Maintains a local book    | None required             | Maintains a local book                 |
| **Detail**       | Aggregated levels            | Aggregated levels         | Top of book               | Individual orders, with queue position |
| **Use case**     | Displays, periodic reads     | Efficient live book       | Price and spread tracking | Market making, queue analysis          |

## Unsubscribe

```json
{
  "method": "unsubscribe",
  "subscription": { "type": "l4BookUpdates", "coins": ["BTC", "ETH"] }
}
```

## Recovery

`l4BookUpdates` carries no per-market `seq` or `prev_seq`; gap detection is server-side, so there is no client sequence bookkeeping. See [detecting gaps](/docs/data/hypercore/concepts/timestamps-cursors-and-replay#detecting-gaps).

* Persist `cursor` after applying a message.
* On reconnect, supply `cursor` in the subscription object.
* A message with `isSnapshot: true` replaces your local state. Any message without it is a diff that must chain onto your previous state.
* If continuity is broken, a fresh message with `isSnapshot: true` is pushed to you. `isSnapshot` is the only continuity signal you need to handle — receiving it means discard local state and adopt the supplied snapshot.