Docs

StreamOrderStatuses - Real-time Order Status Streaming

Stream order lifecycle events from Hyperliquid L1 Gateway via gRPC. Track opens, fills, cancellations, and rejections per block in real time.

Stream order status events starting from a position, providing real-time access to the lifecycle of every order on Hyperliquid: opens, fills, cancellations, and rejections.

Full Code Examples

Clone our gRPC Code Examples Repository for complete, runnable implementations in Go, Python, and Node.js.

When to Use This Method

StreamOrderStatuses is essential for:

  • Order Lifecycle Tracking - Follow an order from open through fill, cancel, or rejection
  • Rejection Analysis - Identify why orders are rejected using the status field
  • Resting-Order Context - Read the full order object behind each status change
  • Compliance and Auditing - Record every status transition with block alignment

Method Signature

protobuf
rpc StreamOrderStatuses(Position) returns (stream BlockOrderStatuses) {}

Response Stream

protobuf
message BlockOrderStatuses {
  // JSON-encoded object matching Hyperliquid's "node_order_statuses_by_block" format.
  bytes data = 1;
}

The data field contains a JSON-encoded block envelope with:

  • Block reference (local_time, block_time, block_number)
  • An events array of status records
  • A full order object behind each status change

Full Order Status Spec

StreamOrderStatuses emits a JSON payload matching Hyperliquid's node_order_statuses_by_block format. The envelope is the same block-envelope shape as fills, but events holds status-record objects rather than [address, fill] tuples.

Top-level keys (always present):

JSON
{
  "local_time": "2026-04-20T00:00:00.040479928",   // ISO-8601 timestamp when node processed the block (nanosecond precision)
  "block_time": "2026-04-19T23:59:59.576040791",    // ISO-8601 timestamp from block consensus
  "block_number": 12345,                            // Block height containing these events
  "events": [                                       // Array of status-record objects
    {
      "time": "2026-04-19T23:59:59.576040791",      // Event timestamp (string, not integer ms)
      "user": "0x1234567890abcdef1234567890abcdef12345678",  // User address
      "hash": "0x00...",                            // Transaction hash (nullable)
      "builder": { "b": "0x00...", "f": 50 },       // Builder address and fee (nullable)
      "status": "open",                             // Status value (see table below)
      "order": { }                                  // Full order object (see Order Object)
    }
  ]
}

Status event fields (events[i]):

FieldTypeDescription
timestringEvent timestamp in the no-Z layout. Note: this is a string, not an integer millisecond value
userstringUser address
hashstring or nullTransaction hash. null for some statuses (for example reduceOnlyCanceled)
builderobject or null{ "b": <address>, "f": <fee> }, the builder address and fee
statusstringStatus value. See Status Values
orderobjectThe resting-order view. See Order Object

Status Values

Observed status values (the list is not exhaustive):

StatusMeaning
openOrder accepted and resting on the book
filledOrder filled
canceledOrder canceled
reduceOnlyCanceledReduce-only order canceled
badAloPxRejectedAdd-liquidity-only order rejected on price
insufficientSpotBalanceRejectedRejected for insufficient spot balance
iocCancelRejectedImmediate-or-cancel order rejected
minTradeNtlRejectedRejected for failing minimum trade notional
perpMarginRejectedRejected for insufficient perp margin
reduceOnlyRejectedReduce-only order rejected
selfTradeCanceledCanceled to prevent a self-trade

Order Object

The order field is the shared resting-order schema, the same object returned by GetOrderBookSnapshot.

FieldTypeDescription
coinstringCoin identifier
sidestring"B" or "A"
limitPxstringLimit price (numeric string)
szstringCurrent size (numeric string)
oidnumberOrder identifier. Not reliably unique across coins. See Order Identifiers
timestampnumberOrder-creation time in Unix milliseconds
triggerConditionstring"N/A" for non-trigger orders
isTriggerbooleanWhether the order is a trigger order
triggerPxstring"0.0" when isTrigger is false
childrenarrayNested trigger-order schedule. Trigger orders (TP/SL) live here, not as top-level entries. Empty for non-trigger parents
isPositionTpslbooleanWhether this is a position-level take-profit / stop-loss
reduceOnlybooleanWhether the order is reduce-only
orderTypestring"Limit", "Trigger", etc.
origSzstringOriginal size
tifstringTime-in-force, for example "Alo", "Gtc", "Ioc"
cloidstring or nullClient order ID

Guarantees and alignment:

  • events contains all status records from the block, across all coins and users.
  • Messages arrive in strictly increasing block_number, one per on-disk block (including blocks with an empty events array).
  • block_number corresponds to abci_block.round in StreamBlocks; block_time aligns with abci_block.time.

Developer tips:

  • Branch on status to track lifecycle transitions and isolate rejections.
  • Handle hash and builder defensively; both can be null.
  • Treat time as a formatted string, distinct from the integer-millisecond time on fill events.

Order Identifiers

oid is not reliably unique across coins. Consumers that index or join per-order state must key on the (coin, oid) tuple, never on oid alone.

Outcome markets (HIP-4) demonstrably reuse oids across the two sides of a single market (for example #30 and #31) within one block. Keying on (coin, oid) is safe whether or not oids turn out to be globally unique elsewhere.

Common Use Cases

1. Rejection Monitor

Python
REJECTIONS = {
    'badAloPxRejected', 'insufficientSpotBalanceRejected', 'iocCancelRejected',
    'minTradeNtlRejected', 'perpMarginRejected', 'reduceOnlyRejected',
}

def count_rejections(block):
    counts = {}
    for event in block.get('events', []):
        status = event['status']
        if status in REJECTIONS:
            counts[status] = counts.get(status, 0) + 1
    return counts

2. Streaming Statuses in Python

Python
import grpc
import json
import os
import hyperliquid_pb2
import hyperliquid_pb2_grpc

def stream_order_statuses():
    endpoint = os.getenv('HYPERLIQUID_ENDPOINT')
    api_key = os.getenv('API_KEY')

    credentials = grpc.ssl_channel_credentials()
    options = [('grpc.max_receive_message_length', 150 * 1024 * 1024)]  # 150MB max
    metadata = [('x-api-key', api_key)]

    with grpc.secure_channel(endpoint, credentials, options=options) as channel:
        client = hyperliquid_pb2_grpc.HyperliquidL1GatewayStub(channel)

        # Empty Position means latest; set block_height or timestamp_ms to backfill
        request = hyperliquid_pb2.Position()

        for response in client.StreamOrderStatuses(request, metadata=metadata):
            block = json.loads(response.data)
            for event in block.get('events', []):
                print(f"{event['status']:>24}  {event['order']['coin']}  oid={event['order']['oid']}")

Error Handling and Reconnection

Reconnect by replaying from the last processed block. Pass the next block_height in the Position so no events are skipped or duplicated.

Python
def stream_with_resume(client, metadata, start_block):
    next_block = start_block
    while True:
        request = hyperliquid_pb2.Position(block_height=next_block)
        try:
            for response in client.StreamOrderStatuses(request, metadata=metadata):
                block = json.loads(response.data)
                process(block)
                next_block = block['block_number'] + 1
        except grpc.RpcError as e:
            print(f"stream error, resuming from {next_block}: {e}")

Best Practices

  1. Connection Management: Implement robust reconnection logic with exponential backoff
  2. Resumable State: Track the last processed block_number so you can resume from the next block
  3. Stable Keys: Index per-order state on (coin, oid) to stay correct across coins
  4. Defensive Parsing: Handle nullable hash and builder and the non-exhaustive status set
  5. Resource Cleanup: Properly close streams and connections on shutdown

Current Limitations

  • Backfill Window: Positioned requests are served from the node's on-disk feed; very old positions may fall outside the retained window. When the feed directory is not configured, positioned requests return NotFound and live tails end immediately.
  • Backpressure: High-volume periods may require careful handling to avoid overwhelming downstream systems

Resources

Need help? Contact our support team or check the Hyperliquid gRPC documentation.