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:
- Production:
wss://api.hypercall.xyz/ws - Local:
ws://localhost:3000/ws
Testnet is temporarily disabled until Hypercall acquires more testnet HYPE.
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 | Allowlisted quote-provider stream. Not generally available yet |
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
Place Order (Authenticated)
Place an order through the WebSocket command path.
{
"type": "PlaceOrder",
"wallet": "0x1234...",
"symbol": "BTC-20260131-100000-C",
"side": "Buy",
"size": "1",
"price": "100",
"tif": "gtc",
"route": "book_only",
"client_id": "my-order-1",
"nonce": 1000,
"signature": "0x..."
}
| Field | Type | Description |
|---|---|---|
wallet | string | Wallet address that owns the order |
symbol | string | Option symbol |
side | string | "Buy" or "Sell" |
size | string | Contract size, exactly matching the signed value |
price | string | Limit price, exactly matching the signed value |
tif | string | Optional time-in-force, defaults to "gtc" |
route | string | Optional route. Use "book_only" for route-aware WebSocket orders. Omitted route remains accepted until at least July 4, 2026. |
client_id | string | Optional client order ID |
nonce | integer | Unique signing nonce |
signature | string | EIP-712 PlaceOrder signature |
WebSocket PlaceOrder currently dispatches directly to the orderbook. route="best_execution" and route="rfq_only" are rejected on WebSocket because this path does not run RPI/RFQ routing yet. Use POST /order for best_execution.
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",
"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. Returns 0 while launch venue fees are disabled |
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. Returns null while launch venue fees are disabled |
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
Allowlisted quote-provider stream with aggregated best bid/ask from registered quote providers. This channel is not generally available yet. Use REST market data and authenticated order/fill/portfolio channels unless Hypercall has enabled quote-provider streaming for your integration.
{
"type": "IndicativeMarketData",
"instrument": "BTC-20260131-100000-C",
"best_bid": "0.0520",
"best_ask": "0.0530",
"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 | Optional best aggregated bid price |
best_ask | string | Optional best aggregated ask price |
bid_iv | number | Optional implied volatility of best bid |
ask_iv | number | Optional implied volatility of best ask |
indicative_bid_size | string | Optional total bid size across providers |
indicative_ask_size | string | Optional 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 a wallet identification message after connecting:
{"type": "Authenticate", "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://api.hypercall.xyz/ws"
async with websockets.connect(uri) as ws:
# Identify the wallet before subscribing to authenticated channels.
await ws.send(json.dumps({
"type": "Authenticate",
"wallet": "0xYourWallet"
}))
# 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://api.hypercall.xyz/ws");
ws.onopen = () => {
ws.send(JSON.stringify({ type: "Authenticate", wallet: "0xYourWallet" }));
// 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`);
}
};