WebSocket API
Real-time data streaming for Hypercall options trading.
Check out the Interactive WebSocket API Reference for a better browsing experience with live examples and schema details.
Download the AsyncAPI specification for programmatic use.
Connection
Connect to wss://HOST/ws:
Endpoints:
- Testnet:
wss://testnet-api.hypercall.xyz/ws - Local:
ws://localhost:3000/ws
Wallet Identification
To receive data on authenticated channels (orders, fills, portfolio), identify your wallet after connecting by sending an Authenticate message:
{"type": "Authenticate", "wallet": "0x1234..."}
The server responds with confirmation:
{"type": "Authenticated", "wallet": "0x1234..."}
After receiving Authenticated, you can subscribe to authenticated channels. If the wallet address is invalid, the server responds with an Error message and the connection remains open.
The ?wallet= query parameter is still supported for backward compatibility but is deprecated and will be removed in a future release. Prefer the message-based approach above.
Connection Liveness
The server enforces a WebSocket heartbeat:
- Sends a
Pingcontrol frame every 20 seconds - Expects a matching
Pongwithin 60 seconds - Closes the connection with close code
1008and reasonpong timeoutif the client stops answering
Browser WebSocket implementations handle ping/pong automatically. Many Rust websocket libraries, including tungstenite and tokio-tungstenite, also handle control-frame ping/pong for you. Check your client's library documentation before adding manual Pong handling. Custom or raw socket implementations must reply to Ping frames with Pong.
Subscribing to Channels
Send a JSON message to subscribe:
{"type": "Subscribe", "channel": "orderbook"}
To unsubscribe:
{"type": "Unsubscribe", "channel": "orderbook"}
You'll receive a confirmation:
{"type": "Subscribed", "channel": "orderbook"}
Symbol Filtering
The order_updates and fills channels support an optional symbols filter. When provided, the server only sends messages whose underlying matches one of the specified symbols.
{"type": "Subscribe", "channel": "order_updates", "symbols": ["BTC"]}
Both bare underlyings ("BTC") and full instrument names ("BTC-20260131-100000-C") are accepted. To add more symbols, send another Subscribe. To remove specific symbols:
{"type": "Unsubscribe", "channel": "order_updates", "symbols": ["BTC"]}
When no symbols are specified, all updates for your wallet are forwarded.
Available Channels
| Channel | Auth Required | Description |
|---|---|---|
orderbook | No | L2 orderbook updates for all symbols |
trades | No | Public trade feed |
market_updates | No | Market listing changes (created/deleted/expired) |
options_chain | No | Incremental options chain updates (filterable by symbols, expiry, option_type) |
candles:<UND>:<RES> | No | Underlying price candles (e.g. candles:BTC:1h) |
index_prices | No | Real-time spot/index prices for all underlyings |
order_updates | Yes | Your order status changes (filterable by symbol) |
fills | Yes | Your trade fills (filterable by symbol) |
portfolio | Yes | Your position and balance updates |
liquidation | Yes | Your liquidation state changes |
competition | Yes | Your competition PnL summary, rank, and final stats |
Message Types
Orderbook Update
L2 orderbook snapshot/update for a symbol.
{
"type": "OrderbookUpdate",
"symbol": "BTC-20260131-100000-C",
"bids": [["95000.5", "10.5"], ["94999.0", "25.0"]],
"asks": [["95001.0", "8.0"], ["95002.5", "15.0"]],
"timestamp": 1737331200000
}
| Field | Type | Description |
|---|---|---|
symbol | string | Option symbol |
bids | array | Bid levels as [price, size] tuples, size is in human-readable contracts |
asks | array | Ask levels as [price, size] tuples, size is in human-readable contracts |
timestamp | integer | Unix timestamp (milliseconds) |
Trade
Public trade event.
{
"type": "Trade",
"symbol": "BTC-20260131-100000-C",
"price": "0.0523",
"size": "5.0",
"side": "buy",
"timestamp": 1737331200000
}
| Field | Type | Description |
|---|---|---|
symbol | string | Option symbol |
price | string | Trade price in USD |
size | string | Trade size in contracts |
side | string | Aggressor side (buy or sell) |
timestamp | integer | Unix timestamp (milliseconds) |
Fill (Authenticated)
Your trade fill notification.
{
"type": "Fill",
"order_id": 12345,
"fill_id": 67890,
"symbol": "BTC-20260131-100000-C",
"side": "buy",
"price": "0.0523",
"size": "5.0",
"timestamp": 1737331200000,
"wallet_address": "0x1234...abcd",
"fee": "0.0005",
"trade_id": 99999,
"is_taker": true
}
| Field | Type | Description |
|---|---|---|
order_id | integer | Your order ID |
fill_id | integer | Fill ID |
symbol | string | Option symbol |
side | string | Trade side (buy or sell) |
price | string | Fill price in USD |
size | string | Fill size in contracts |
timestamp | integer | Unix timestamp (milliseconds) |
wallet_address | string | Your wallet address |
fee | string | Trading fee charged |
trade_id | integer | Unique trade ID |
is_taker | boolean | Whether you were the taker |
builder_code_address | string? | Builder code wallet (if any) |
builder_code_fee | string? | Builder code fee (if any) |
Portfolio Update (Authenticated)
Portfolio stream update for positions, balances, margin, and Greeks.
Greeks update example:
{
"type": "PortfolioUpdate",
"timestamp": 1737331200000,
"per_leg": [
{
"symbol": "BTC-20260131-100000-C",
"quantity": "2.0",
"delta": 0.91,
"gamma": 0.003,
"theta": -0.12,
"vega": 0.44,
"iv": 0.63
}
],
"aggregate": {
"delta": 0.91,
"gamma": 0.003,
"theta": -0.12,
"vega": 0.44,
"iv": 0.63
}
}
For empty portfolios, Greeks updates use:
per_leg: []aggregate: null
Competition PnL Summary (Authenticated)
Competition stream update for header/footer PnL display.
{
"type": "CompetitionPnlSummary",
"wallet_address": "0x1234...abcd",
"lifetime_realized_pnl": "1250.50",
"active_competition": {
"competition_id": 7,
"competition_name": "Spring Sprint",
"competition_state": "active",
"rank": 12,
"pnl": "420.25",
"volume": "25000",
"efficiency": "0.01681",
"medal": null
},
"timestamp": 1737331200000
}
When there is no active competition, active_competition is null.
Order Update (Authenticated)
Order status change notification.
{
"type": "OrderUpdate",
"order_id": 12345,
"client_order_id": "my-order-1",
"status": "filled",
"filled_size": "10.0",
"remaining_size": "0",
"avg_fill_price": "0.0523"
}
Market Update
Market listing changes.
Market Created:
{
"type": "MarketUpdate",
"action": "Created",
"symbol": "BTC-20260131-100000-C",
"strike": "100000",
"is_call": true,
"underlying": "BTC",
"expiry": 1738281600,
"timestamp": 1737331200000
}
Market Expired:
{
"type": "MarketUpdate",
"action": "Expired",
"symbol": "BTC-20260131-100000-C",
"strike": "100000",
"is_call": true,
"underlying": "BTC",
"expiry": 1738281600,
"timestamp": 1738281600000
}
Position Expired (Authenticated)
Notification when your position settles at expiry.
{
"type": "PositionExpired",
"wallet_address": "0x1234...abcd",
"symbol": "BTC-20260131-100000-C",
"position_size": "10.0",
"settlement_price": "105000",
"settlement_value": "500.0",
"timestamp": 1738281600000
}
Liquidation State Change (Authenticated)
Your account liquidation state change.
{
"type": "LiquidationStateChange",
"wallet_address": "0x1234...abcd",
"previous_state": "Normal",
"new_state": "Warning",
"equity": "10000.0",
"mm_required": "9500.0",
"shortfall": "0",
"auction_id": null,
"timestamp": 1737331200000
}
| State | Description |
|---|---|
Normal | Account is healthy |
Warning | Approaching margin call |
Liquidating | Liquidation auction active |
Error
Server error message.
{
"type": "Error",
"message": "Invalid channel: foobar"
}
Authentication
Authenticated channels require the wallet query parameter in the connection URL:
wss://testnet-api.hypercall.xyz/ws?wallet=0x1234567890abcdef...
Messages on authenticated channels are filtered to only show data for your wallet. No signature is required for WebSocket connections.
Example: Python Client
import asyncio
import websockets
import json
async def main():
uri = "wss://testnet-api.hypercall.xyz/ws?wallet=0xYourWallet"
async with websockets.connect(uri) as ws:
# Subscribe to orderbook
await ws.send(json.dumps({
"type": "Subscribe",
"channel": "orderbook"
}))
# Subscribe to fills for BTC only
await ws.send(json.dumps({
"type": "Subscribe",
"channel": "fills",
"symbols": ["BTC"]
}))
# Listen for messages
async for message in ws:
data = json.loads(message)
print(f"Received: {data['type']}")
asyncio.run(main())
Example: TypeScript Client
const ws = new WebSocket(
"wss://testnet-api.hypercall.xyz/ws?wallet=0xYourWallet"
);
ws.onopen = () => {
// Subscribe to channels
ws.send(JSON.stringify({ type: "Subscribe", channel: "orderbook" }));
// Subscribe to order updates filtered to BTC
ws.send(JSON.stringify({
type: "Subscribe",
channel: "order_updates",
symbols: ["BTC"],
}));
};
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
console.log(`Received: ${msg.type}`);
if (msg.type === "OrderbookUpdate") {
console.log(`${msg.symbol}: ${msg.bids.length} bids, ${msg.asks.length} asks`);
}
};