Bybit Spot wire format
How Bybit Spot messages become rows in our Parquet files. Each data type below shows a real message next to the rows we publish for it, with the source and the rule for every column. Hover a line or a column to see how they connect.
Checked against our pipeline
These messages were captured from Bybit Spot on 2026-10-05. Our test suite replays each one through the production collector parser and postprocessor, and fails if the published rows differ from the rows on this page. Files written before a change to the pipeline can differ; see Known Gaps & Corrections.
The collector subscribes to orderbook.50.<symbol>, publicTrade.<symbol> and tickers.<symbol> for every trading spot pair. Messages are parsed and postprocessed by the same code as Bybit Futures.
- Public WebSocket:
wss://stream.bybit.com/v5/public/spot - REST (trade backfill):
https://api.bybit.com
Exchange API reference: bybit-exchange.github.io. Conventions shared by every exchange are on the wire formats overview.
Trades
One row per trade from the publicTrade.<symbol> topic, plus trades recovered from REST after a reconnect.
- WebSocket publicTrade.<symbol>
- REST GET /v5/market/recent-trade
Trade message
Bybit batches trades: this message carries 2, and each becomes its own row.
{"topic": "publicTrade.BTCUSDT","ts": 1791227700350,→ event_time"type": "snapshot","data": [{"i": "2290000001224566789",→ trade_id"T": 1791227700349,→ trade_time"p": "85663.7",→ price"v": "0.0024",→ quantity"S": "Sell",→ is_buyer_maker"seq": 114993707179,"s": "BTCUSDT",→ symbol"BT": false,"RPI": false},{"i": "2290000001224566790",→ trade_id"T": 1791227700349,→ trade_time"p": "85663.7",→ price"v": "0.002853",→ quantity"S": "Sell",→ is_buyer_maker"seq": 114993707179,"s": "BTCUSDT",→ symbol"BT": false,"RPI": false}]}
Published rows · 2
ts, when Bybit generated the push. Milliseconds. Shared by every trade in the message.true when the taker side S is Sell, false when it is Buy.Market. A placeholder: Bybit sends no order type.REST trade backfill
Bybit does not replay trades on subscribe. After every trade connection (re)opens, the collector fetches the latest trades from GET /v5/market/recent-trade and publishes those it did not receive over the WebSocket. A backfilled trade whose trade_time falls outside the hour of the file is not published.
Abridged: Requested with limit=1 to keep the example short; the collector requests limit=60, the most Bybit returns.
{"retCode": 0,"retMsg": "OK","result": {"category": "spot","list": [{"execId": "2290000001224568695",→ trade_id"symbol": "BTCUSDT",→ symbol"price": "85782.8",→ price"size": "0.005246",→ quantity"side": "Sell",→ is_buyer_maker"time": "1791227915828",→ event_time · trade_time"isBlockTrade": false,"isRPITrade": false,"seq": "114993797995"}]},"retExtInfo": {},"time": 1791227915979}
Published row
time: REST responses have no push time, so event_time equals trade_time on backfilled rows. Milliseconds.true when the taker side side is Sell, false when it is Buy.Market. A placeholder: Bybit sends no order type.Notes
- Rows are sorted by
trade_time, then Bybit's cross sequenceseq, thentrade_id. - The block-trade flag
BT, the RPI flagRPI, the tick directionLandseqare not published. Block trades and RPI trades are published as ordinary rows. - A trade received over both the WebSocket and the REST backfill is published once, with the WebSocket values. On a backfilled row
event_timeequalstrade_time, andreceived_timecan be seconds after the trade. - Bybit's REST endpoint returns only the latest 60 spot trades, so a longer reconnect gap is only partly recovered.
Column types and descriptions: Trades schema.
Order Book
One row per price level from the orderbook.50.<symbol> topic, published as Bybit sent it.
- WebSocket orderbook.50.<symbol>
Book snapshot
Bybit sends a snapshot of the 50-level book when the subscription starts, and again after a problem on its side or a service restart (u = 1). Each level becomes a snapshot row.
Abridged: Bybit sent 50 bid and 50 ask levels; the best three of each are shown.
{"topic": "orderbook.50.BTCUSDT",→ symbol"ts": 1791227698276,→ event_time"type": "snapshot",→ event_type"data": {"s": "BTCUSDT","b": [→ side["85666.4", "0.134992"],→ side · price · quantity["85666.3", "0.000915"],→ side · price · quantity["85665.9", "0.006238"]→ side · price · quantity],"a": [→ side["85666.5", "0.596838"],→ side · price · quantity["85666.7", "0.024785"],→ side · price · quantity["85667.8", "0.000835"]→ side · price · quantity],"u": 311718506,→ final_update_id"seq": 114993705748→ last_update_id},"cts": 1791227698274→ transaction_time}
Published rows · 6
orderbook.50.BTCUSDT). Equals data.s.snapshot for type: "snapshot": every level of the 50-level book. A message with u = 1 (Bybit service restart) is also a snapshot.u + 1.seq. It orders messages but is not contiguous.bid for levels in b, ask for levels in a. One row per level.Book delta
{"topic": "orderbook.50.BTCUSDT",→ symbol"ts": 1791227698296,→ event_time"type": "delta",→ event_type"data": {"s": "BTCUSDT","b": [→ side["85666.4", "0.139984"],→ side · price · quantity["85662.9", "0"],→ side · price · quantity["85662.2", "0.00447"]→ side · price · quantity],"a": [→ side["85668.3", "0.000111"],→ side · price · quantity["85693.8", "0.075584"],→ side · price · quantity["85694.5", "0"]→ side · price · quantity],"u": 311718507,→ first_update_id · final_update_id"seq": 114993705774→ last_update_id},"cts": 1791227698295→ transaction_time}
Published rows · 6
orderbook.50.BTCUSDT). Equals data.s.update for type: "delta".u. Each delta covers exactly one update ID, so first_update_id = final_update_id.u; a larger step is a gap.seq. It orders messages but is not contiguous.bid for levels in b, ask for levels in a. One row per level.0 removes the level.Notes
- The collector subscribes to the 50-level book only, and WebSocket snapshots anchor it. Bybit's REST depth snapshots carry the update IDs of its 1,000-level stream, so they cannot anchor this book and are not published.
- The collector checks that each delta's
uis one more than the previous message's before it writes. After a gap it reconnects and starts again from a new WebSocket snapshot. - A delta with no levels is published as one row with
side=noopandpriceandquantity0, so the update-ID chain has no holes. Skip these rows when rebuilding the book. - Files can start with a checkpoint snapshot of the book carried over from the previous hour: its
received_timeis the hour start,transaction_timeis null,final_update_idis the lastuapplied andlast_update_iditsseq. - Quantities are in base asset units (BTC for BTCUSDT). Bybit does not include Retail Price Improvement (RPI) orders in the book stream.
- Files written before the October 2026 pipeline update can also contain
snapshotrows built from REST depth snapshots. Their update IDs belong to Bybit's 1,000-level stream and do not continue the 50-level chain; skip them when replaying.
Column types and descriptions: Order Book schema.
Ticker
One row per tickers.<symbol> message.
- WebSocket tickers.<symbol>
Bybit spot tickers are always full snapshots, so each row comes from one message.
{"topic": "tickers.BTCUSDT","ts": 1791227696127,→ event_time"type": "snapshot","cs": 114993705064,"data": {"symbol": "BTCUSDT",→ symbol"lastPrice": "85666.5",→ price_change · last_price"highPrice24h": "86996.9",→ high_price"lowPrice24h": "84978.5",→ low_price"prevPrice24h": "85386",→ price_change · open_price"volume24h": "7322.439121",→ base_asset_volume"turnover24h": "630464015.32595089",→ quote_asset_volume"price24hPcnt": "0.0033",→ price_change_percent"usdIndexPrice": "85662.421591"}}
Published row
lastPrice − prevPrice24h, computed exactly in decimal.0.0033 becomes 0.33.prevPrice24h, the price 24 hours ago.Notes
usdIndexPriceis not published.price_change_percentis in percent from the 2026-09 data-integrity release; earlier files hold Bybit's fraction.
Column types and descriptions: Ticker schema.
Not published
- Mark Price: Bybit spot tickers carry no mark price or funding rate.
- Open Interest: Spot markets have no open interest.
- Liquidations: Bybit's
allLiquidationtopic covers derivatives only.