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.

Additive schema evolution

WebSocket message objects may gain new fields without advance notice. Clients must ignore fields they do not recognize. Hypercall does not remove fields from an existing message schema.

Connection​

Connect to wss://HOST/ws:

Endpoints:

  • Production: wss://api.hypercall.xyz/ws
  • Local: ws://localhost:3000/ws
Testnet status

Testnet is temporarily disabled until Hypercall acquires more testnet HYPE.

Compression​

/ws supports RFC 7692 permessage-deflate. Enable it in your client to cut market-data bandwidth: on captured production traffic, independently compressed frames are about 45% smaller, and index price batches about 70% smaller.

  • Your client offers the extension in the upgrade request, for example Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits.
  • When the server accepts, the 101 response carries Sec-WebSocket-Extensions: permessage-deflate; server_no_context_takeover. Each server message is then compressed on its own, without a shared sliding window, and messages under a few hundred bytes are still sent uncompressed. Your library decompresses transparently.
  • If the response has no Sec-WebSocket-Extensions header, every frame is uncompressed. Compression is enabled per environment, so clients must handle both; the JSON messages are identical either way.
  • Your client may compress what it sends. The server inflates each client message up to 256 KiB and closes the connection if a message would exceed that.

Browsers negotiate the extension automatically. Most libraries need no change:

Python (websockets enables compression by default):

import websockets

async with websockets.connect("wss://api.hypercall.xyz/ws", compression="deflate") as ws:
...

Node.js (ws):

import WebSocket from "ws";

const ws = new WebSocket("wss://api.hypercall.xyz/ws", { perMessageDeflate: true });

Rust: the hypercall-client WsClient negotiates permessage-deflate by default (see Example: Rust Client). Plain tungstenite and tokio-tungstenite do not implement it yet, so they connect uncompressed. For compression with your own socket, use a client that implements it, such as yawc:

let url = "wss://api.hypercall.xyz/ws".parse()?;
let mut ws = yawc::WebSocket::connect(url)
.with_options(yawc::Options::default().with_low_latency_compression())
.await?;

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.

Slow Consumer Recovery​

The server closes a /ws connection that cannot drain outbound data within the configured message, encoded-byte, queue-age, or socket-write safety ceiling. When the connection can still accept a close frame, the server uses code 1008 and a compact JSON reason:

{"error":"slow_consumer","class":"ordered_public","cause":"message_age","recovery":"snapshot_resubscribe"}

The reason fields are:

FieldMeaning
classDelivery class whose frame crossed the safety boundary.
causemessage_limit, byte_limit, message_age, or write_timeout.
recoveryRequired next action, such as resubscribe, snapshot_resubscribe, portfolio_refetch, or rest_reconcile.

After any disconnect, reconnect, identify the wallet again when required, resubscribe, and reconcile current state before processing new events. Ordered public channels require a fresh snapshot. Private event channels require reconciliation through the authoritative REST surface because cursor replay is not available yet. A fully stalled connection may terminate before it can read the close reason, so clients must use this recovery flow for an unclean close too.

Use separate connections for high-rate public market data and authenticated commands or private streams. Delivery classes select metrics and recovery behavior, but frames on one connection still share one ordered socket write path. A stalled public write can therefore delay later private frames on that same connection until the write deadline closes it.

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.

The public orderbook, trades, indicative_market_data, and indicative_market_data_v2 channels also accept symbols, matched against exact instrument symbols (case-insensitive), not underlyings:

{"type": "Subscribe", "channel": "orderbook", "symbols": ["BTC-20260131-100000-C"]}

Without symbols, these channels deliver every instrument.

Options Chain Filtering​

The options_chain channel supports filtering by instrument symbols, expiry date, and option type:

{
"type": "Subscribe",
"channel": "options_chain",
"symbols": ["BTC-20260131-100000-C"],
"expiry": "2026-01-31",
"option_type": "call"
}
FilterValuesDefault
symbolsArray of full instrument symbols (e.g., ["BTC-20260131-100000-C"])All instruments
expiryDate string "YYYY-MM-DD", matched against the UTC calendar date of each instrument's expiryAll expiries
option_type"call", "put", "both", or omit for bothBoth

Batched Delivery​

indicative_market_data_v2 carries the same rows as indicative_market_data, several rows per frame:

{"type": "Subscribe", "channel": "indicative_market_data_v2", "symbols": ["BTC-20260131-100000-C"]}

The server confirms with a batch marker:

{"type": "Subscribed", "channel": "indicative_market_data_v2", "batch": true}

It then holds each row for at most one batch window, 25 ms by default, and sends the rows published in that window as one IndicativeMarketDataBatch frame. A batch is also sent early once it reaches 256 rows.

  • Detecting support: only a confirmation with "batch": true means the server batches. Servers released before this channel confirm any channel name without the marker and then send nothing, and current servers answer an unknown channel with an unknown_channel error. In either case, unsubscribe from indicative_market_data_v2 and subscribe to indicative_market_data on the same connection.
  • Latency: a row arrives up to one window later than on indicative_market_data. On production traffic with the default window, rows waited 12 ms on average and never more than 25 ms. Use the v1 channel if you act on individual quote updates within milliseconds.
  • Bandwidth: batching alone saves about 7% of bytes. The large saving comes with permessage-deflate: compressed batches are about 9.8 times smaller than uncompressed one-row frames for an unfiltered subscription, and about 8.3 times smaller for one options chain, against about 1.8 times for rows compressed one by one.
  • Order: rows in updates are in publish order, and batches arrive in order. Rows are not conflated, so one instrument can appear more than once in a batch. Apply the rows in order and you reach the same state as on the v1 channel.
  • Snapshot: the subscribe-time snapshot also arrives as batch frames.
  • Filters: symbols works as on indicative_market_data. Narrowing the filter or unsubscribing first sends any rows already pending under the old filter, then the Unsubscribed confirmation, so no row is lost or duplicated.
  • Independent channels: a connection can subscribe to both channels with different filters. indicative_market_data is unchanged.

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)
index_pricesNoReal-time spot/index prices for all underlyings
indicative_market_dataNoAllowlisted quote-provider stream. Not generally available yet
indicative_market_data_v2NoThe same stream in batch frames. See Batched Delivery
order_updatesYesYour order status changes (filterable by symbol)
fillsYesYour trade fills (filterable by symbol)
portfolioYesYour position and balance updates
liquidationYesYour liquidation state changes
rfqYesRFQ quotes, status updates, and fill notifications
experimental_order_updates, experimental_combo_fillsYesExperimental combo channels. See Experimental Combo API
experimental_combo_tradesNoExperimental public combo trades

Competition and leaderboard data are not served by api.hypercall.xyz, over WebSocket or REST. Subscribing to the retired competition or competition_engagement channels returns an Error. The trading app reads competitions over HTTP from the analytics service at https://api-ui.hypercall.xyz, for example GET /v2/competitions, GET /v2/competitions/{id}, GET /v2/competitions/leaderboard, and GET /v2/competitions/all-time/leaderboard.

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": 1780000000000,
"signature": "0x..."
}

The nonce above is illustrative. Generate a fresh one for every order from the current Unix time in milliseconds, for example Date.now() in JavaScript. A copied fixed value falls outside the accepted window and is rejected.

FieldTypeDescription
walletstringWallet address that owns the order
symbolstringOption symbol
sidestring"Buy" or "Sell"
sizestringContract size, exactly matching the signed value
pricestringLimit price, exactly matching the signed value
tifstringOptional time-in-force, defaults to "gtc"
routestringUse "book_only". An omitted route is still accepted but deprecated, and its sunset date has passed.
client_idstringOptional client order ID
reduce_onlybooleanOptional, default false. A reduce-only order signs PlaceOrderReduceOnly and requires an explicit route
mmp_enabledbooleanOptional, default false
builder_code_addressstringOptional builder code wallet
nonceintegerRequired signing nonce: the current Unix time in milliseconds. It must fall within two days before and one day after the current time. Each nonce is accepted once per signer, and REST and WebSocket orders share the same nonce space. A replayed frame is rejected.
signaturestringEIP-712 PlaceOrder signature

The connection must have sent Authenticate first; otherwise the server answers {"type": "Error", "message": "Not authenticated"}. The signer must be wallet or an approved agent for it.

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.

An admitted order is answered on the same connection with an OrderResult:

{
"type": "OrderResult",
"order_id": 12345,
"status": "ACKED",
"symbol": "BTC-20260131-100000-C",
"side": "Buy",
"price": "100",
"size": "1"
}

reason is added when the order is not accepted. Validation, signature, and rate-limit failures are sent as an Error whose message starts with Order placement failed:. Subsequent status changes arrive on order_updates.

Orderbook Update​

L2 orderbook snapshot/update for a symbol.

{
"type": "OrderbookUpdate",
"symbol": "BTC-20260131-100000-C",
"option_token_address": "0x...",
"bids": [["95000.5", "10.5"], ["94999.0", "25.0"]],
"asks": [["95001.0", "8.0"], ["95002.5", "15.0"]],
"timestamp": 1737331200000
}
FieldTypeDescription
symbolstringOption symbol
option_token_addressstring?On-chain option token address, when known
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",
"trade_id": 99999,
"is_taker": true,
"builder_code_address": null,
"builder_code_fee": null,
"instrument_type": "option"
}
FieldTypeDescription
order_idintegerYour order ID
fill_idintegerFill ID
symbolstringOption symbol
sidestringYour side of the trade (Buy or Sell)
pricestringFill price in USD
sizestringFill size in contracts
timestampintegerUnix timestamp (milliseconds)
wallet_addressstringYour wallet address
feestringTrading fee charged. Returns 0 while launch venue fees are disabled
trade_idintegerUnique trade ID
is_takerbooleanWhether you were the taker
builder_code_addressstring?Builder code wallet (if any)
builder_code_feestring?Builder code fee. Returns null while launch venue fees are disabled
instrument_typestring"option"

Portfolio Update (Authenticated)​

Sent on the portfolio channel for positions, balances, margin, and Greeks. On subscribe the server sends an Initial snapshot, then incremental updates.

Each frame's type is its update kind:

Update kindFields besides timestamp
Initialpositions
PositionUpdateposition
BalanceUpdatetotal_margin_used
MarginUpdatespan_margin, margin_summary, margin_mode, total_margin_used, available_balance
GreeksUpdateper_leg, aggregate

timestamp on portfolio frames is Unix seconds, unlike the millisecond timestamps on other channels.

Greeks update example:

{
"type": "GreeksUpdate",
"timestamp": 1737331200,
"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

Order Update (Authenticated)​

Order status change notification. PARTIALLY_FILLED updates are included. filled_size is the cumulative quantity filled across the order's executions, not the quantity of the latest fill.

{
"type": "OrderUpdate",
"order_id": 12345,
"client_id": "my-order-1",
"request": {
"price": "0.0523",
"size": "10.0",
"symbol": "BTC-20260131-100000-C",
"side": "Buy",
"tif": "gtc"
},
"status": "PARTIALLY_FILLED",
"filled_size": "0.75",
"timestamp": 1767225600000,
"reason": null,
"wallet_address": "0x0000000000000000000000000000000000000001",
"instrument_type": "option"
}

status is one of ACKED, OPEN, PARTIALLY_FILLED, FILLED, CANCELED, or REJECTED. client_id is omitted when the order has none. instrument_type is "option" or "perp".

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
}

action is Created, Expired, Deleted (carries only symbol and timestamp), or ComboCreated (see Experimental Combo API).

Options Chain Update​

Sent on options_chain when a listing or its top of book changes. action is Upsert or Remove.

{
"type": "OptionsChainUpdate",
"action": "Upsert",
"currency": "BTC",
"expiry": 1769846400,
"row": {
"strike": 100000.0,
"call": {
"symbol": "BTC-20260131-100000-C",
"bid_price_usd": 1520.0,
"ask_price_usd": 1580.0
}
},
"timestamp": 1737331200000
}

An Upsert row carries one leg (call or put) with the leg fields of the options chain snapshot: symbol, option_token_address, bid and ask price, IV, size in contracts and USD notional, greeks_abs, and greeks_cash (the example above is abbreviated). Values that are not available are null; the leg that is not part of the update is omitted. A Remove carries currency, expiry, strike, option_type, symbol, and timestamp when an instrument is deleted, expires, or enters pending settlement. expiry is Unix seconds.

Position Expired (Authenticated)​

Sent on the portfolio channel 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",
"settlement_entry_price": "100.0",
"cost_basis": "1000.0",
"net_pnl": "-500.0",
"timestamp": 1738281600000
}

settlement_entry_price, cost_basis, and net_pnl are null when not available.

Liquidation State Change (Authenticated)​

Sent on the liquidation channel when your account's liquidation state changes.

{
"type": "LiquidationStateChange",
"wallet_address": "0x1234...abcd",
"previous_state": "healthy",
"new_state": "pre_liquidation",
"liquidation_mode": "partial",
"margin_mode": "standard",
"equity": "10000.0",
"mm_required": "10500.0",
"maintenance_margin": "-500.0",
"shortfall": "500.0",
"partial_liquidation": { "...": "..." },
"full_liquidation": null,
"timestamp": 1737331200000
}
FieldDescription
previous_state, new_statehealthy, pre_liquidation, in_liquidation, or liquidated
liquidation_modepartial, full, or null when no liquidation flow is active
margin_modestandard or portfolio
equityAccount equity
mm_requiredMaintenance margin requirement
maintenance_marginEquity minus the maintenance margin requirement
shortfallAmount by which the account is under maintenance margin
partial_liquidationPartial liquidation progress (entry time, target equity, escalation deadline, active liquidation orders, bonus), or null
full_liquidationFull liquidation auction details (auction ID, start time, margin needed, and on completion the winner, bonus, and resolution transaction), or null

See Liquidation for what each state means.

Index Price Update​

Batched spot/index prices for all underlyings.

{
"type": "IndexPriceUpdate",
"prices": [
{"underlying": "BTC", "price": "97250.50", "timestamp": 1737331200000},
{"underlying": "ETH", "price": "3200.00", "timestamp": 1737331199980},
{"underlying": "NVDA", "price": "182.40", "timestamp": 1737331199950}
],
"timestamp": 1737331199950
}
FieldTypeDescription
pricesarrayArray of {underlying, price, timestamp} entries for each tracked underlying
prices[].underlyingstringUnderlying symbol (e.g., "BTC", "ETH")
prices[].pricestringCurrent spot/index price in USD
prices[].timestampintegerSource observation time for this underlying, as Unix milliseconds
timestampintegerOldest source observation time included in prices, as Unix 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,
"published_at": 1737331200004,
"timestamp": 1737331200000
}
FieldTypeDescription
instrumentstringOption symbol
best_bidstringOptional best aggregated bid price
best_askstringOptional best aggregated ask price
bid_ivnumberOptional implied volatility of best bid
ask_ivnumberOptional implied volatility of best ask
indicative_bid_sizestringOptional total bid size across providers
indicative_ask_sizestringOptional total ask size across providers
num_providersintegerNumber of active quote providers
rfq_provider_quotesarrayOptional per-provider quotes: wallet, bid_price, ask_price, max_bid_size, max_ask_size, updated_at
published_atintegerAPI-clock time this row was published, Unix milliseconds. Measure the age of rfq_provider_quotes against this, not your local clock
timestampintegerTime of the latest provider quote, Unix milliseconds

Indicative Market Data Batch​

Sent on the indicative_market_data_v2 channel (see Batched Delivery). Each element of updates has the same fields as an IndicativeMarketData message, without type.

{
"type": "IndicativeMarketDataBatch",
"flushed_at": 1737331200025,
"updates": [
{
"instrument": "BTC-20260131-100000-C",
"best_bid": "0.0520",
"best_ask": "0.0530",
"num_providers": 3,
"published_at": 1737331200004,
"timestamp": 1737331200000
},
{
"instrument": "BTC-20260131-105000-C",
"best_bid": "0.0310",
"best_ask": "0.0322",
"num_providers": 2,
"published_at": 1737331200004,
"timestamp": 1737331200001
}
]
}
FieldTypeDescription
flushed_atintegerAPI-clock time the batch was sent, Unix milliseconds. flushed_at - published_at is the batching delay of a row
updatesarrayRows published during the window, oldest first

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"
}

Clock Sync​

Send ClockSync with any nonce string to get an API-clock sample on the same connection, for example to bound transport delay:

{"type": "ClockSync", "nonce": "abc"}
{"type": "ClockSynced", "nonce": "abc", "server_at": 1737331200000}

server_at is Unix milliseconds. No authentication is needed.

Error​

Server error message. Match on code when present, not on message.

{
"type": "Error",
"message": "Unknown channel 'foobar'",
"code": "unknown_channel",
"channel": "foobar"
}
FieldTypeDescription
messagestringHuman-readable description
codestringOptional stable reason. unknown_channel: a Subscribe named a channel that does not exist
channelstringOptional. The channel a rejected Subscribe named

A Subscribe to a channel that is not listed in Available Channels is answered with the unknown_channel error, and no subscription is created. Earlier servers confirmed any name and then sent nothing.

Other errors carry only message. Examples: Authentication required for this channel when you subscribe to an authenticated channel before Authenticate, Not authenticated for PlaceOrder before Authenticate, and Invalid wallet address: ....

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`);
}
};

Example: Rust Client​

The hypercall-client crate's WsClient identifies the wallet, restores subscriptions after a reconnect, and answers server pings for you. It offers permessage-deflate compression by default and falls back to uncompressed frames when the server does not accept it, with the same JSON messages either way. Call with_permessage_deflate(false) to stop offering it.

use hypercall_client::WsClient;

#[tokio::main]
async fn main() -> hypercall_client::Result<()> {
let ws = WsClient::new();
ws.connect("https://api.hypercall.xyz", None).await?;
ws.subscribe(vec!["orderbook", "index_prices"]).await?;

if let Some(msg) = ws.wait_for_message(|_| true, 5_000).await {
println!("Received: {msg:?}");
}
Ok(())
}