Timestamps
Read each timestamp in the context of its object. Lighter exposes block time, transaction execution time and message publication time, with units that vary by field.
Start with the object and the unit
Trade.timestampis block time in milliseconds.Order.timestampis block time in seconds. A WebSocket envelope'stimestampis the API server's frame-generation time in milliseconds. None of these gives the time your order arrived at the API server.
Where the timestamps come from
The sequencer records a block timestamp when the block opens, before its first transaction. Trades in that block share this timestamp. Each transaction also has a finer-grained transaction_time, recorded in microseconds when it executes.
Sequencer
Block time appears as Trade.timestamp and Tx.executed_at in milliseconds, and as Order.timestamp in seconds. The order field reflects the block time of the order's last update.
transaction_time records transaction execution time in microseconds. last_updated_at carries an aggregate of these execution timestamps for a market's order-book batch.
API server
envelope.timestamp records frame-generation time in milliseconds on channels that expose it. The documented trade envelope has no publication timestamp. Record a local receive time if you need to track when it reaches your client.
Queue timing has a separate source, described below.
queued_at uses the millisecond component of a Redis stream ID; a pending transaction can instead carry an API-server-time placeholder. Don't treat it as another sequencer timestamp.
Measure client latency on your own clock
Subtracting an API-server or Redis timestamp from a sequencer timestamp includes an unknown clock offset. For client-observed latency, use a local monotonic clock at submission and at the specific response or confirmation you want to measure. An acknowledgement and an execution confirmation measure different stages.
Seconds, milliseconds and microseconds
Choose the conversion by field and object. The service that supplies a value doesn't determine its unit.
| Field | Native unit | Convert to milliseconds |
|---|---|---|
Order.timestamp | seconds | value * 1_000 |
Trade.timestamp, Tx.executed_at | milliseconds | No conversion |
envelope.timestamp, Tx.queued_at | milliseconds | No conversion |
transaction_time, book/ticker last_updated_at | microseconds | value / 1_000 |
Python · field-specific conversions
# Trade: retain integer microseconds for exact arithmetic.
block_us = trade["timestamp"] * 1_000
execution_us = trade["transaction_time"]
since_block_timestamp_us = execution_us - block_us
# Full Order objects use seconds for this particular field.
order_block_ms = order["timestamp"] * 1_000
# Tx.executed_at and envelope.timestamp are already milliseconds.
tx_block_ms = tx["executed_at"]
frame_ms = message["timestamp"] # only if the envelope has this fieldInteger division such as transaction_time // 1_000 discards sub-millisecond precision. Treat an unavailable value such as 0 as missing timing data, rather than converting it into a date.
Field reference
| Field and context | Source | Unit | What it marks |
|---|---|---|---|
timestamp · WebSocket envelope | API server | ms | When the API server generates the frame. This does not give the client's receive time. |
timestamp · Trade | Sequencer block time | ms | The timestamp of the block containing the trade. Trades in the same block share it. |
timestamp · Full Order | Sequencer block time | s | Block time of the order's last update, in seconds. This isn't an API ingress time. |
transaction_time | Sequencer | µs | Transaction execution time. Its presence alone doesn't establish that an order filled. |
last_updated_at · Order book / ticker | Sequencer-derived aggregate | µs | Maximum transaction_time across orders changed in the latest processed order-book batch for the market. Book and ticker read the same stored value. |
executed_at · Transaction | Sequencer block time | ms | The block timestamp shared by transactions in the block. It isn't a millisecond conversion of the per-transaction transaction_time. |
queued_at · Transaction | Redis stream / pending placeholder | ms | Time from the Redis stream ID. While pending, the value can be an API-server-clock placeholder. |
This table covers the fields and contexts listed here. Other time fields, including candle open times and expiry values, need their own definitions.
Block time and execution time
Reading a trade
Both timestamps inside a Trade come from the sequencer. timestamp records the block's opening time at millisecond resolution; transaction_time records that transaction's execution time at microsecond resolution.
For the trade in the public WebSocket example:
| Event | Field | Value |
|---|---|---|
| Block opens | Trade.timestamp | 1773854156654 ms |
| Transaction executes | transaction_time | 1773854156686065 µs |
The block timestamp also supplies Tx.executed_at. Other trades in the same block share the block time.
After conversion, the difference from the block timestamp is 32.065 ms. This measures time relative to the block timestamp, with millisecond precision on the block side. It does not measure API ingress delay or client network latency.
trade/0 · abridged JSON
{
"channel": "trade:0",
"nonce": 8630448841,
"trades": [{
"trade_id": 16164557907,
"market_id": 0,
"size": "0.1336",
"price": "2181.83",
"block_height": 198321831,
"timestamp": 1773854156654,
"transaction_time": 1773854156686065
}],
"type": "update/trade"
}For this block/execution pair, compare timestamp * 1_000 with transaction_time. Don't compare the raw numbers in different units, or use timestamps as unique sequence identifiers.
No API ingress timestamp on a Trade
A trade does not expose the original request's arrival time. Liquidations, stop-loss/take-profit triggers and expiry cancellations can originate inside the sequencer; those internal actions don't pass through the API edge as client submissions.
The trade envelope has no publication timestamp in the public schema. A local arrival timestamp records when your client received the message; it cannot tell you when the server sent it.
Market data publication
On order_book and ticker, the outer timestamp records API-server frame generation. The last_updated_at value describes sequencer activity represented by the market's processed batch.
order_book/0 · abridged JSON
{
"channel": "order_book:0",
"last_updated_at": 1774884082309144,
"offset": 1558300,
"order_book": {
"asks": [{ "price": "2064.54", "size": "0.3285" }],
"bids": [],
"nonce": 9182390020,
"begin_nonce": 9182389998,
"last_updated_at": 1774884082309144
},
"timestamp": 1774884082326,
"type": "update/order_book"
}The normalized numeric difference in this example is 16.856 ms. Because the two fields use different clocks, that number cannot isolate batching, serialization or network delay.
Envelope timestamps also appear on market_stats, candle, user_stats, spot_market_stats, mark_price_candle, height and account_all_assets. Check the schema of the channel you consume; not every envelope includes this field.
How book and ticker use last_updated_at
The standard order_book channel batches updates every 50 ms. The market cache takes the maximum transaction_time over every order changed in the processed batch, including changes away from the best bid or offer. Ticker reads that same stored value.
For example, suppose a market batch contains these order changes:
| Underlying order change | Transaction execution time |
|---|---|
| Order A changes at the best bid | T + 1,000 µs |
| Order B changes deeper in the book | T + 8,000 µs |
The stored market last_updated_at is T + 8,000 µs. Order book and ticker both read this value for the corresponding market state, even though Order B is outside the best bid and offer.
Public price levels contain price and size, with no per-level transaction_time. The times above belong to the underlying order changes and illustrate the batch aggregation.
Compare book and ticker values for the corresponding nonce/state. Messages received next to each other may describe different updates, so arbitrary cross-channel pairs need not match.
Continuity and freshness are separate checks
For consecutive order-book updates, verify that
begin_nonceequals the previousnonce. That detects missing updates; it doesn't establish how old the latest message is.Track local time since the last received update for stream freshness. An envelope timestamp can help assess publication age if you account for client/server clock offset. Neither a recent frame nor a gap-free sequence alone proves that the underlying market state is recent.
Which field should I use?
| You need | Use | Limit |
|---|---|---|
| Execution time associated with a fill | The Trade's transaction_time | µs; identify the actual fill from its trade record. |
| Block time | Trade.timestamp or Tx.executed_at | ms; shared within a block. Order.timestamp uses seconds. |
| Frame-generation time | Envelope timestamp, where present | ms, API-server clock. Absent from the documented trade envelope. |
| Client receive time | Your local timestamp | Cannot recover server-send time from it. |
| Order-book continuity | Current begin_nonce vs previous nonce | Apply within the same market stream; recover from gaps with a new snapshot. |
| Event ordering | The relevant stream's nonce or transaction sequence_index | Respect each counter's scope. offset belongs to an API server and can jump on reconnect. |
| Client-observed response latency | Local monotonic clock at both ends | Specify whether you stop timing at ack, execution confirmation or fill. |
REST endpoints and acknowledgements
recentTrades and trades return Trade objects: their timestamp is block time in ms, while transaction_time uses µs. Full Order objects use seconds for timestamp.
orderBookOrders returns SimpleOrder objects. This schema includes transaction_time, but neither timestamp nor last_updated_at.
Known limitation: orderBookOrders
transaction_timeis currently not populated and returns0. Treat this value as unavailable timing data; don't use it as an execution timestamp.
A successful submission response from sendTx or sendTxBatch acknowledges submission. The WebSocket send response can include a tx_hash and predicted_execution_time_ms; the prediction is not an actual execution timestamp.
Confirm the outcome you need
Check the transaction's status and result to establish execution. To establish a fill, check the trade record and the order's status and filled amounts. An executed order-placement or cancellation transaction can have a
transaction_timewithout producing a fill.
Integration checklist
- Map each timestamp to its object and unit. Convert seconds, milliseconds and microseconds explicitly.
- Keep block time, transaction execution time, queue time and frame-generation time separate.
- Use stream-specific sequence information for ordering and
begin_noncefor book continuity. Track freshness separately. - Measure client latency with a local monotonic clock; don't infer server latency across different clocks.
- Handle missing or zero timing values and confirm fills from execution results and trade/order data.
Updated 8 days ago
