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.
Options Chain Filtering
The options_chain channel supports filtering by underlying symbols, expiry date, and option type:
{
"type": "Subscribe",
"channel": "options_chain",
"symbols": ["BTC-20260131-100000-C"],
"expiry": "2026-01-31",
"option_type": "call"
}
| Filter | Values | Default |
|---|---|---|
symbols | Array of full instrument symbols (e.g., ["BTC-20260131-100000-C"]) | All instruments |
expiry | Date string "YYYY-MM-DD" | All expiries |
option_type | "call", "put", or omit for both | Both |
Candle Channel Format
Candle subscriptions require the underlying and resolution in the channel name:
{"type": "Subscribe", "channel": "candles:BTC:1h"}
Format: candles:<UNDERLYING>:<RESOLUTION>
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 |
indicative_market_data | No | Aggregated quote provider bid/ask for all instruments |
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 |
competition_engagement | Yes | Rank changes, gap-to-next-rank, and final standings |
rfq | Yes | RFQ quotes, status updates, and fill notifications |
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 |
Index Price Update
Batched spot/index prices for all underlyings.
{
"type": "IndexPriceUpdate",
"prices": [
{"underlying": "BTC", "price": "97250.50"},
{"underlying": "ETH", "price": "3200.00"},
{"underlying": "HYPE", "price": "28.50"}
],
"timestamp": 1737331200000
}
| Field | Type | Description |
|---|---|---|
prices | array | Array of {underlying, price} entries for each tracked underlying |
prices[].underlying | string | Underlying symbol (e.g., "BTC", "ETH") |
prices[].price | string | Current spot/index price in USD |
timestamp | integer | Unix timestamp (milliseconds) |
Indicative Market Data
Aggregated best bid/ask from registered quote providers. Published for all instruments with active quotes.
{
"type": "IndicativeMarketData",
"instrument": "BTC-20260131-100000-C",
"best_bid": "0.0520",
"best_ask": "0.0530",
"bid_iv": 0.62,
"ask_iv": 0.64,
"indicative_bid_size": "50.0",
"indicative_ask_size": "25.0",
"num_providers": 3,
"timestamp": 1737331200000
}
| Field | Type | Description |
|---|---|---|
instrument | string | Option symbol |
best_bid | string | Best aggregated bid price |
best_ask | string | Best aggregated ask price |
bid_iv | number | Implied volatility of best bid |
ask_iv | number | Implied volatility of best ask |
indicative_bid_size | string | Total bid size across providers |
indicative_ask_size | string | Total ask size across providers |
num_providers | integer | Number of active quote providers |
timestamp | integer | Unix timestamp (milliseconds) |
Competition Rank Change (Authenticated)
Notification when your rank changes in an active competition.
{
"type": "CompetitionRankChange",
"wallet_address": "0x1234...abcd",
"competition_id": 7,
"from_rank": 15,
"to_rank": 12,
"delta_places": 3,
"pnl": "420.25",
"timestamp": 1737331200000
}
Competition Gap Update (Authenticated)
Distance to the next rank above you.
{
"type": "CompetitionGapUpdate",
"wallet_address": "0x1234...abcd",
"competition_id": 7,
"rank": 12,
"next_rank": 11,
"gap_metric_value": "50.00",
"timestamp": 1737331200000
}
Competition Final Standing (Authenticated)
Sent when a competition ends with your final results.
{
"type": "CompetitionFinalStanding",
"wallet_address": "0x1234...abcd",
"competition_id": 7,
"rank": 12,
"pnl": "420.25",
"volume": "25000",
"efficiency": "0.01681",
"medal": null,
"timestamp": 1737331200000
}
RFQ Quotes (Authenticated)
Quotes received in response to your RFQ submission.
{
"type": "RfqQuotes",
"rfq_id": "550e8400-e29b-41d4-a716-446655440000",
"quotes": [
{
"quote_id": "660e8400-e29b-41d4-a716-446655440001",
"net_premium": "52.30",
"expires_at": 1737331225000
}
],
"status": "quoted",
"taker_wallet": "0x1234...abcd"
}
RFQ Status Update (Authenticated)
Status change for an RFQ you submitted.
{
"type": "RfqStatusUpdate",
"rfq_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "executed",
"taker_wallet": "0x1234...abcd"
}
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`);
}
};