Trading API & SDK · Placing Orders · 5/8
WebSocket Streams
Why WebSockets?
REST API polling is fine for one-off queries, but for real-time trading you need WebSockets:
- Order book updates — Fresh book snapshots pushed as blocks are produced
- Trade stream — Get notified of every trade
- User events — Your fills, funding payments, liquidations and non-user cancels
- Low latency — No HTTP overhead on each message
Connecting
The SDK's Info.subscribe(subscription, callback) manages this connection for you (see basic_ws.py). To see the raw protocol, here is a minimal client using the websocket-client package:
import json
import threading
import websocket # pip install websocket-client
WS_URL = "wss://api.hyperliquid.xyz/ws"
def on_message(ws, message):
data = json.loads(message)
print(data)
def on_open(ws):
print("Connected!")
# Subscribe to channels after connection
subscribe_msg = {
"method": "subscribe",
"subscription": {"type": "l2Book", "coin": "BTC"}
}
ws.send(json.dumps(subscribe_msg))
ws = websocket.WebSocketApp(
WS_URL,
on_open=on_open,
on_message=on_message,
)
# Run in a separate thread
ws_thread = threading.Thread(target=ws.run_forever)
ws_thread.daemon = True
ws_thread.start()
Subscription Types
L2 Order Book
{"method": "subscribe", "subscription": {"type": "l2Book", "coin": "BTC"}}
Each message is a full snapshot of the top levels (up to 20 per side), not an incremental diff. Snapshots are pushed on new blocks, no more than once every 0.5 seconds.
All Mid Prices
{"method": "subscribe", "subscription": {"type": "allMids"}}
Streams mid prices for all assets as {"mids": {coin: price}}. Useful for portfolio valuation. Add "dex": "xyz" to get mids for a HIP-3 dex.
Trades
{"method": "subscribe", "subscription": {"type": "trades", "coin": "BTC"}}
Every trade that occurs on the BTC market.
User Events
{"method": "subscribe", "subscription": {"type": "userEvents", "user": "0xYourAddress"}}
Fills, funding payments, liquidations and non-user cancels for an address. No signature is needed: user streams are public data for any address. Note that messages for this subscription arrive on the channel named "user". For fills alone, userFills is a simpler stream.
Candles
{"method": "subscribe", "subscription": {"type": "candle", "coin": "BTC", "interval": "1m"}}
Processing Messages
def on_message(ws, message):
data = json.loads(message)
if data.get("channel") == "l2Book":
book = data["data"]
bids = book["levels"][0]
asks = book["levels"][1]
if bids and asks:
spread = float(asks[0]["px"]) - float(bids[0]["px"])
print(f"BTC spread: ${spread:.2f}")
elif data.get("channel") == "trades":
for trade in data["data"]:
side = "BUY" if trade["side"] == "B" else "SELL" # side is "B" (buy) or "A" (sell/ask)
print(f"{side} {trade['sz']} BTC @ {trade['px']}")
elif data.get("channel") == "user": # userEvents subscription
event = data["data"] # one object, e.g. {"fills": [...]} or {"funding": {...}}
for fill in event.get("fills", []):
print(f"Filled: {fill['coin']} {fill['sz']} @ {fill['px']}")
Best Practices
- Reconnection logic — The server may disconnect at any time without notice. Reconnect, resubscribe, and backfill gaps with the matching
/inforequest. - Heartbeats — The server closes a connection that has received nothing from it for 60 seconds. On quiet channels, send
{"method": "ping"}; the reply is{"channel": "pong"}(docs). - Message queuing — Process messages in a separate thread to avoid blocking the WebSocket.
- Error handling — Always handle connection errors and malformed messages gracefully.
- Limits — As of October 2026, each IP may hold up to 10 WebSocket connections and 1000 subscriptions, and send up to 2000 messages per minute (rate limits).