Skip to main content

WebSocket API

Real-time data streaming for Hypercall options trading.

Interactive Reference

Check out the Interactive WebSocket API Reference for a better browsing experience with live examples and schema details.

Machine-Readable Spec

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.

Deprecated: Query Parameter Authentication

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 Ping control frame every 20 seconds
  • Expects a matching Pong within 60 seconds
  • Closes the connection with close code 1008 and reason pong timeout if 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

ChannelAuth RequiredDescription
orderbookNoL2 orderbook updates for all symbols
tradesNoPublic trade feed
market_updatesNoMarket listing changes (created/deleted/expired)
options_chainNoIncremental options chain updates (filterable by symbols, expiry, option_type)
candles:<UND>:<RES>NoUnderlying price candles (e.g. candles:BTC:1h)
index_pricesNoReal-time spot/index prices for all underlyings
order_updatesYesYour order status changes (filterable by symbol)
fillsYesYour trade fills (filterable by symbol)
portfolioYesYour position and balance updates
liquidationYesYour liquidation state changes
competitionYesYour 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
}
FieldTypeDescription
symbolstringOption symbol
bidsarrayBid levels as [price, size] tuples, size is in human-readable contracts
asksarrayAsk levels as [price, size] tuples, size is in human-readable contracts
timestampintegerUnix timestamp (milliseconds)

Trade

Public trade event.

{
"type": "Trade",
"symbol": "BTC-20260131-100000-C",
"price": "0.0523",
"size": "5.0",
"side": "buy",
"timestamp": 1737331200000
}
FieldTypeDescription
symbolstringOption symbol
pricestringTrade price in USD
sizestringTrade size in contracts
sidestringAggressor side (buy or sell)
timestampintegerUnix 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
}
FieldTypeDescription
order_idintegerYour order ID
fill_idintegerFill ID
symbolstringOption symbol
sidestringTrade side (buy or sell)
pricestringFill price in USD
sizestringFill size in contracts
timestampintegerUnix timestamp (milliseconds)
wallet_addressstringYour wallet address
feestringTrading fee charged
trade_idintegerUnique trade ID
is_takerbooleanWhether you were the taker
builder_code_addressstring?Builder code wallet (if any)
builder_code_feestring?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
}
StateDescription
NormalAccount is healthy
WarningApproaching margin call
LiquidatingLiquidation 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`);
}
};