WebSocket API
WebSocket API
Overview
The WebSocket API allows real-time communication with our platform. Use it to stream market data, subscribe to updates, and interact with the order book.
Key Identifier Types
The WebSocket API uses two primary identifier types throughout the trading system:
-
order_id: Unique identifier for individual limit orders. Generated as a 32-character hexadecimal string when a limit order is posted to the orderbook. Used for order management, cancellation, and tracking order lifecycle events.
-
trade_id: Unique identifier for executed trades. Generated as a 32-character hexadecimal string when a market order is filled against limit orders. Used for tracking individual trade executions and settlement records.
Relationship: Multiple trades can reference the same order_id when a single limit order is filled across multiple market orders (partial fills). Each trade execution generates a unique trade_id.
Key Timestamp Types
The WebSocket API uses two timestamp formats that are important to understand:
- timestamp_ms: Event wrapper timestamps in milliseconds (13 digits) - used for event ordering and processing
- timestamp: Data payload timestamps, usually in milliseconds (13 digits)
- creation_timestamp: Order/request creation times in seconds (10 digits)
Learn More: For detailed timestamp format explanations, see the Timestamp Formats section.
Learn More: For detailed explanations and trading scenarios, see the Order ID vs Trade ID Guide.
| Environment | WebSocket URL |
|---|---|
| Staging | wss://staging.kyan.sh/ws |
| Production | wss://api.kyan.sh/ws |
Quick Start
- Connect to the WebSocket endpoint
- Authenticate with your API key
- Subscribe to market data channels
- Monitor your trades and account state
- Execute trades via REST API while monitoring via WebSocket
Supported Markets
- Trading Pairs: BTC_USDC, ETH_USDC, ARB_USDC
- Base Tokens: BTC, ETH, ARB
- Instruments: Options and Perpetual Futures
Instrument Naming Format
Options
- Format:
{PAIR}-{MATURITY}-{STRIKE}-{TYPE} - Examples:
BTC_USDC-31OCT25-130000-C(BTC call option)ETH_USDC-31OCT25-4700-P(ETH put option)ARB_USDC-31OCT25-0d300-C(ARB call option with decimal strike)
Perpetuals
- Format:
{PAIR}-PERPETUAL - Examples:
BTC_USDC-PERPETUAL,ETH_USDC-PERPETUAL,ARB_USDC-PERPETUAL
Format Components
- PAIR:
{BASE}_{QUOTE}where BASE isBTC,ETH, orARBand QUOTE isUSDC - MATURITY:
DDMMMYYformat (e.g., "31OCT25" for October 31, 2025)- Months: JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC
- STRIKE: Integer for BTC/ETH, decimal notation with "d" for ARB (e.g.,
1d500for 1.5) - TYPE:
Cfor Call options,Pfor Put options
Trading Workflow
1. Authentication
All WebSocket connections must be authenticated before accessing any data. Send an AUTH message immediately after connecting.
Learn More: For detailed API key setup and management, see the API Key Guide.
import WebSocket from 'ws';
const wsUrl = 'wss://staging.kyan.sh/ws';
const API_KEY = 'your-api-key-here';
const authMessage = {
type: 'auth',
api_key: API_KEY,
};
const ws = new WebSocket(wsUrl);
ws.onopen = () => {
console.log('WebSocket connection open');
ws.send(JSON.stringify(authMessage));
ws.onmessage = (event) => {
const response = JSON.parse(event.data.toString());
console.log('Authentication response:', response);
if (response.success) {
// Continue with subscriptions
}
};
};2. Market Discovery
After authentication, discover available instruments using get_instruments:
// Get all available instruments for BTC
const message = {
type: 'get_instruments',
markets: ['BTC'],
};
ws.send(JSON.stringify(message));3. Subscribe to Market Data
Subscribe to the channels you need for trading:
// Subscribe to instruments, index prices, and orderbook
const subscribeMessage = {
type: 'subscribe',
subscriptions: [
{
channel: 'instruments',
query: { market: 'BTC' },
},
{
channel: 'index_price',
query: { pair: 'BTC_USDC' },
},
{
channel: 'orderbook_perps',
query: { pair: 'BTC_USDC' },
},
],
};
ws.send(JSON.stringify(subscribeMessage));4. Monitor Your Account
Track your trades and account activity:
// Subscribe to your account's trades and transfers
const accountSubscribe = {
type: 'subscribe',
subscriptions: [
{
channel: 'trade',
query: { account: 'your-account-address' },
},
{
channel: 'transfer',
query: { account: 'your-account-address' },
},
],
};
ws.send(JSON.stringify(accountSubscribe));Available Channels
Channels are organized by their use in a trading workflow:
Market Data Channels
| Channel | Description | Query Parameters |
|---|---|---|
index_price | Underlying asset price updates | pair: Trading pair (e.g., "BTC_USDC") |
instruments | Available instruments updates | market (optional): Base token (e.g., "BTC", "ETH", "ARB"). If omitted, subscribes to all markets |
orderbook_options | Option orderbook updates | instrument_name or pair (optional), maturity, strike, type, direction, options (see Query Options below) |
orderbook_perps | Perpetual orderbook updates | instrument_name or pair (optional), direction, options (see Query Options below) |
orderbook_maker | Maker-specific orderbook | maker (required - Ethereum address), pair (optional) |
funding | Funding rate updates | instrument_name: Perpetual instrument name |
interest_rate | Interest rate changes | pair (required), expiry (optional) |
iv | Implied volatility updates | pair (required), maturity (optional) |
Account-Specific Channels
| Channel | Description | Query Parameters |
|---|---|---|
trade | Trade execution events | account (optional - EOA or Smart account address), pair (optional), direction (optional): "buy" or "sell" |
transfer | Deposit and withdrawal events | account (required - EOA or Smart account address), symbol (optional), type (optional): "deposit" or "withdrawal" |
account_state | Account state updates | account (required - EOA or Smart account address), pair (optional) |
account_liquidation | Account liquidation events | account (required - EOA or Smart account address), pair (optional) |
bankruptcy | Account bankruptcy events | market (optional): Trading pair filter |
mmp | Market Maker Protection events | smart_account_address (optional): Smart account address, pair (optional): Trading pair filter |
Trading & Maker Channels
| Channel | Description | Query Parameters |
|---|---|---|
rfq | Request-for-quote updates | account (optional): Smart account address, type (optional): "request" or "response", order_id (optional) |
orderbook_maker | Maker-specific orderbook events | maker (required): Ethereum address of the market maker, pair (optional): Trading pair filter |
Query Options
Some channels support additional query options to control subscription behavior:
| Option | Type | Default | Description |
|---|---|---|---|
skip_snapshot | boolean | false | Skip the initial orderbook snapshot when subscribing. Only applies to orderbook_options and orderbook_perps channels. When set to true, you'll only receive incremental updates (post_order, cancel_order, trade events) without the initial state. |
Example with Query Options:
{
"channel": "orderbook_options",
"query": {
"pair": "BTC_USDC",
"options": {
"skip_snapshot": true
}
}
}Event Types
When subscribed to channels, you'll receive events with the following types:
| Event Type | Description | Associated Channel |
|---|---|---|
index_price | Index price update | index_price |
instruments | Instruments list update | instruments |
post_order | New order posted | orderbook_options, orderbook_perps, orderbook_maker |
cancel_order | Order cancellation | orderbook_options, orderbook_perps, orderbook_maker |
ob_maker_orders | Maker orders snapshot | orderbook_maker |
trade | Trade execution | trade |
deposit | Deposit to account | transfer |
withdrawal | Withdrawal from account | transfer |
funding | Funding rate update | funding |
interest_rate | Interest rate update | interest_rate |
svi | Stochastic Volatility update | iv |
rfq_request | New RFQ request | rfq |
rfq_post_response | New response to RFQ | rfq |
rfq_cancel_response | Cancelled RFQ response | rfq |
account_state | Account state update | account_state |
account_liquidation | Liquidation event | account_liquidation |
bankruptcy | Account bankruptcy event | bankruptcy |
mmp_triggered | MMP triggered event | mmp |
Message Types
All WebSocket messages support an optional id field for request-response correlation. When provided, the server will include the same id in the response, allowing you to match requests with their corresponding responses.
Example with Request ID:
{
"type": "auth",
"id": "auth-request-1",
"api_key": "your-api-key-here"
}Response with Request ID:
{
"kind": "response",
"type": "auth",
"id": "auth-request-1",
"timestamp_ms": 1677721600000,
"success": true
}Authentication Messages
AUTH
Authenticate your WebSocket connection.
Request:
{
"type": "auth",
"api_key": "your-api-key-here", // Alphanumeric pattern: ^[a-zA-Z0-9_]+$
"id": "optional-request-id"
}Note: A valid API key must contain only letters, numbers, or underscores.
(pattern: ^[a-zA-Z0-9_]+$).
Learn More: For API key format requirements and troubleshooting, see the API Key Guide.
Success Response:
{
"kind": "response",
"type": "auth",
"id": "optional-request-id",
"timestamp_ms": 1677721600000,
"success": true
}Error Responses:
- Invalid API key format:
{
"kind": "response",
"type": "error",
"id": "optional-request-id",
"timestamp_ms": 1677721600000,
"success": false,
"error": "invalid auth payload [{\"instancePath\":\"/api_key\",\"schemaPath\":\"#/properties/api_key/pattern\",\"keyword\":\"pattern\",\"params\":{\"pattern\":\"^[a-zA-Z0-9_]+$\"},\"message\":\"must match pattern \\\"^[a-zA-Z0-9_]+$\\\"\"}]"
}- API key not found or invalid:
{
"kind": "response",
"type": "auth",
"id": "optional-request-id",
"timestamp_ms": 1677721600000,
"success": false,
"error": "NOT_FOUND"
}- Other validation errors:
{
"kind": "response",
"type": "auth",
"id": "optional-request-id",
"timestamp_ms": 1677721600000,
"success": false,
"error": "Failed to validate api key"
}Possible error codes:
NOT_FOUND- API key doesn't existFORBIDDEN- API key is disabledKEY_USAGE_EXCEEDED- API key has exceeded usage limitsRATELIMITED- Too many requestsSession already authorized- Already authenticated on this connection
logout
End your current session.
Request:
{
"type": "logout"
}Response:
{
"kind": "response",
"type": "logout",
"id": "optional-request-id",
"timestamp_ms": 1677721600000,
"success": true
}Market Data Messages
get_instruments
Retrieve available instruments for specified markets.
Request:
{
"type": "get_instruments",
"markets": ["BTC", "ETH", "ARB"]
}Note: The markets array should contain base tokens only (not pairs).
Response:
{
"kind": "response",
"type": "get_instruments",
"id": "optional-request-id",
"timestamp_ms": 1677721600000,
"success": true,
"instruments": [
"BTC_USDC-31OCT25-130000-C",
"BTC_USDC-PERPETUAL",
"ETH_USDC-31OCT25-4700-P"
]
}get_ob_state_by_instruments
Get the current orderbook state for specified instruments.
Request:
{
"type": "get_ob_state_by_instruments",
"instrument_names": ["BTC_USDC-31OCT25-130000-C", "BTC_USDC-PERPETUAL"]
}Note: The instrument_names parameter is optional. If not provided, returns orderbook state for all instruments.
Response:
Both get_ob_state_by_instruments and get_ob_state_by_market return the same response format with type get_ob_state.
get_ob_state_by_market
Get the current orderbook state for all instruments in a specific market.
Request:
{
"type": "get_ob_state_by_market",
"market": "BTC",
"maturity": "31OCT25"
}Note: The maturity parameter is optional. If provided, filters instruments by expiration date.
Orderbook State Response Format
Both get_ob_state_by_instruments and get_ob_state_by_market requests return responses with type get_ob_state. The response structure varies by instrument type:
For Options:
{
"kind": "response",
"type": "get_ob_state",
"id": "optional-request-id",
"timestamp_ms": 1677721600000,
"success": true,
"state": {
"BTC_USDC-31OCT25-130000-C": {
"timestamp": 1677721600000,
"instrument_name": "BTC_USDC-31OCT25-130000-C",
"index_price": 45000.5,
"open_interest": 150.5,
"last_price": 2500.0,
"best_bid_price": 2495.0,
"best_bid_amount": 1.5,
"best_ask_price": 2505.0,
"best_ask_amount": 1.0,
"bids": [
[2495.0, 1.5],
[2490.0, 2.0]
],
"asks": [
[2505.0, 1.0],
[2510.0, 1.8]
],
"positions": 25,
"mark_iv": 0.65,
"bid_iv": 0.64,
"ask_iv": 0.66,
"interest_rate": 0.05,
"settlement_price": 0,
"greeks": {
"delta": 0.45,
"gamma": 0.0012,
"theta": -45.5,
"vega": 125.3,
"rho": 85.2
}
}
}
}For Perpetuals:
{
"kind": "response",
"type": "get_ob_state",
"id": "optional-request-id",
"timestamp_ms": 1677721600000,
"success": true,
"state": {
"BTC_USDC-PERPETUAL": {
"timestamp": 1677721600000,
"instrument_name": "BTC_USDC-PERPETUAL",
"index_price": 45000.5,
"open_interest": 2500000,
"last_price": 45010.0,
"current_funding": 0.0001,
"best_bid_price": 45000.0,
"best_bid_amount": 20000,
"best_ask_price": 45020.0,
"best_ask_amount": 30000,
"bids": [
[45000.0, 20000],
[44980.0, 40000]
],
"asks": [
[45020.0, 30000],
[45040.0, 20000]
]
}
}
}Subscription Management Messages
get_subscriptions
List your active subscriptions.
Request:
{
"type": "get_subscriptions"
}Response:
{
"kind": "response",
"type": "get_subscriptions",
"id": "optional-request-id",
"timestamp_ms": 1677721600000,
"success": true,
"subscriptions": [
{
"channel": "index_price",
"query": {
"pair": "BTC_USDC"
}
},
{
"channel": "orderbook_options",
"query": {
"instrument_name": "BTC_USDC-31OCT25-130000-C"
}
}
]
}subscribe
Subscribe to specific data channels.
Request:
{
"type": "subscribe",
"subscriptions": [
{
"channel": "index_price",
"query": {
"pair": "BTC_USDC"
}
},
{
"channel": "orderbook_options",
"query": {
"instrument_name": "BTC_USDC-31OCT25-130000-C"
}
}
]
}Response:
{
"kind": "response",
"type": "subscribe",
"id": "optional-request-id",
"timestamp_ms": 1677721600000,
"success": true,
"subscriptions": [
{
"channel": "index_price",
"query": {
"pair": "BTC_USDC"
}
},
{
"channel": "orderbook_options",
"query": {
"instrument_name": "BTC_USDC-31OCT25-130000-C"
}
}
]
}unsubscribe
Unsubscribe from specific data channels.
Request:
{
"type": "unsubscribe",
"subscriptions": [
{
"channel": "index_price",
"query": {
"pair": "BTC_USDC"
}
}
]
}Response:
{
"kind": "response",
"type": "unsubscribe",
"id": "optional-request-id",
"timestamp_ms": 1677721600000,
"success": true,
"subscriptions": [
{
"channel": "index_price",
"query": {
"pair": "BTC_USDC"
}
}
]
}unsubscribe_all
Unsubscribe from all data channels.
Request:
{
"type": "unsubscribe_all"
}Response:
{
"kind": "response",
"type": "unsubscribe_all",
"id": "optional-request-id",
"timestamp_ms": 1677721600000,
"success": true
}Subscription Filtering
Many channels support advanced filtering to reduce the volume of events you receive:
Orderbook Filtering
For orderbook_options and orderbook_perps channels, you can filter events by:
pair(optional): Subscribe to all instruments for a specific trading pairdirection: Filter for only "buy" or "sell" ordersoptions.skip_snapshot: Skip initial orderbook state, receive only incremental updates
Note: Maker filtering has been moved to the dedicated orderbook_maker channel (see below)
Example - Subscribe to all BTC options with buy orders:
{
"channel": "orderbook_options",
"query": {
"pair": "BTC_USDC",
"direction": "buy"
}
}Example - Subscribe to all perpetual markets without initial snapshots:
{
"channel": "orderbook_perps",
"query": {
"options": {
"skip_snapshot": true
}
}
}Subscription Query Parameters
index_price Channel
Monitor real-time price updates for trading pairs.
{
"channel": "index_price",
"query": {
"pair": "BTC_USDC"
}
}orderbook_options Channel
Stream option orderbook updates. Two formats are supported:
Using instrument name directly:
{
"channel": "orderbook_options",
"query": {
"instrument_name": "BTC_USDC-31OCT25-130000-C",
"direction": "buy",
"options": {
"skip_snapshot": true
}
}
}Or using specific parameters (all optional for broad subscriptions):
{
"channel": "orderbook_options",
"query": {
"pair": "BTC_USDC",
"maturity": "31OCT25",
"strike": 70000,
"type": "C",
"direction": "buy",
"options": {
"skip_snapshot": false
}
}
}Note:
- When using
instrument_name, it must match the pattern:{PAIR}-{MATURITY}-{STRIKE}-{TYPE} - All parameters except
instrument_nameare optional when not using instrument name format - Use
options.skip_snapshotto skip initial orderbook snapshot (default: false)
orderbook_perps Channel
Stream perpetual futures orderbook updates. Two formats are supported:
Using instrument name directly:
{
"channel": "orderbook_perps",
"query": {
"instrument_name": "BTC_USDC-PERPETUAL",
"direction": "buy",
"options": {
"skip_snapshot": true
}
}
}Or using the pair (optional for broad subscription):
{
"channel": "orderbook_perps",
"query": {
"pair": "BTC_USDC",
"direction": "buy",
"options": {
"skip_snapshot": false
}
}
}Note:
- All parameters except
instrument_nameare optional when not using instrument name format - Use
options.skip_snapshotto skip initial orderbook snapshot (default: false)
Note: When using instrument_name, it must match the pattern: {PAIR}-PERPETUAL
trade Channel
Track executed trades for specific accounts.
{
"channel": "trade",
"query": {
"account": "0x1234567890abcdef1234567890abcdef12345678",
"pair": "BTC_USDC",
"direction": "buy"
}
}Note:
- The account address must include the 0x prefix and be exactly 42 characters total
- Use the
directionparameter for filtering trades by direction - Valid values for
direction: "buy", "sell"
transfer Channel
Monitor deposits and withdrawals for an account.
{
"channel": "transfer",
"query": {
"account": "0x1234567890abcdef1234567890abcdef12345678",
"symbol": "USDC",
"type": "deposit"
}
}Note:
- The
symbolparameter is optional. Valid values: "ETH", "BTC", "USDC", "ARB" - The
typeparameter is optional. Valid values: "deposit", "withdrawal"
rfq Channel
Monitor RFQ (Request for Quote) requests and responses.
Query Parameters:
account(optional): Smart account address - filters RFQ events for this accounttype(optional): "request" or "response" - filters by RFQ event typeorder_id(optional): Specific RFQ order ID to monitor (32 hexadecimal characters)
Examples:
Monitor all RFQ requests from a specific account:
{
"channel": "rfq",
"query": {
"account": "0x1234567890abcdef1234567890abcdef12345678",
"type": "request"
}
}Monitor all RFQ responses visible to an account:
{
"channel": "rfq",
"query": {
"account": "0x1234567890abcdef1234567890abcdef12345678",
"type": "response"
}
}Monitor all RFQ requests (market maker perspective):
{
"channel": "rfq",
"query": {
"type": "request"
}
}Monitor a specific RFQ order:
{
"channel": "rfq",
"query": {
"order_id": "863509b1a865fa3f502a9f6784122dd7"
}
}Monitor all RFQ activity:
{
"channel": "rfq",
"query": {}
}Note: The order_id must be exactly 32 hexadecimal characters. Use different query combinations based on your role (taker vs maker) and what RFQ events you need to monitor.
orderbook_maker Channel
Monitor orderbook events specifically for a market maker's orders. This dedicated channel provides:
- Initial snapshot of all existing maker orders
- Real-time updates for all orders posted/cancelled by the specified maker
- Efficient tracking for market makers managing their own orders
Query Parameters:
maker(required): Ethereum address of the market maker (42 characters with 0x prefix)pair(optional): Filter by trading pair (BTC_USDC, ETH_USDC, or ARB_USDC)
Examples:
Monitor all orders for a specific maker:
{
"channel": "orderbook_maker",
"query": {
"maker": "0x1234567890abcdef1234567890abcdef12345678"
}
}Monitor maker orders for a specific trading pair:
{
"channel": "orderbook_maker",
"query": {
"maker": "0x1234567890abcdef1234567890abcdef12345678",
"pair": "BTC_USDC"
}
}Important Notes:
- This channel replaces the previous
makerfiltering option inorderbook_optionsandorderbook_perpschannels - Returns both options and perpetual orders for the specified maker
- Initial snapshot event type is
ob_maker_orderscontaining all active orders from the maker - After the initial snapshot, you'll receive real-time
post_orderandcancel_orderevents for that maker's activity - Designed for market makers to efficiently monitor their own order activity
funding Channel
Monitor funding rate updates for perpetual futures.
{
"channel": "funding",
"query": {
"instrument_name": "BTC_USDC-PERPETUAL"
}
}
Learn More: For detailed information about funding rates and calculations, see the Funding Rate Specification.
interest_rate Channel
Track interest rate changes for trading pairs.
{
"channel": "interest_rate",
"query": {
"pair": "BTC_USDC",
"expiry": "31OCT25"
}
}Note: The expiry parameter is optional. If not provided, returns interest rate updates for all expiries.
iv Channel
Monitor implied volatility updates.
{
"channel": "iv",
"query": {
"pair": "BTC_USDC",
"maturity": "31OCT25"
}
}Note: The maturity parameter is optional. If not provided, returns IV updates for all maturities.
account_state Channel
Track account state changes.
{
"channel": "account_state",
"query": {
"account": "0x1234567890abcdef1234567890abcdef12345678",
"pair": "BTC_USDC"
}
}Note: The pair parameter is optional. If not provided, returns account state updates for all pairs.
account_liquidation Channel
Monitor account liquidation events.
{
"channel": "account_liquidation",
"query": {
"account": "0x1234567890abcdef1234567890abcdef12345678",
"pair": "BTC_USDC"
}
}Note: The pair parameter is optional. If not provided, returns liquidation events for all pairs.
bankruptcy Channel
Monitor account bankruptcy events. Bankruptcy occurs when an account's equity becomes negative after liquidation attempts.
{
"channel": "bankruptcy",
"query": {
"market": "BTC_USDC"
}
}Query Parameters:
market(optional): Trading pair (BTC_USDC, ETH_USDC, ARB_USDC, or USDC_USDC)
Examples:
Monitor bankruptcy events for a specific market:
{
"channel": "bankruptcy",
"query": {
"market": "ETH_USDC"
}
}Monitor all bankruptcy events:
{
"channel": "bankruptcy",
"query": {}
}mmp Channel
Monitor Market Maker Protection (MMP) triggered events. MMP temporarily freezes an account's trading when risk thresholds are exceeded.
{
"channel": "mmp",
"query": {
"smart_account_address": "0x1234567890abcdef1234567890abcdef12345678",
"pair": "BTC_USDC"
}
}Query Parameters:
smart_account_address(optional): Smart account address (42 characters with 0x prefix)pair(optional): Trading pair (BTC_USDC, ETH_USDC, or ARB_USDC)
Examples:
Monitor MMP events for a specific account:
{
"channel": "mmp",
"query": {
"smart_account_address": "0x1234567890abcdef1234567890abcdef12345678"
}
}Monitor MMP events for a specific trading pair:
{
"channel": "mmp",
"query": {
"pair": "ETH_USDC"
}
}Monitor all MMP events:
{
"channel": "mmp",
"query": {}
}Timestamp Formats
WebSocket events use consistent timestamp formatting across all message types:
Event Wrapper Timestamps:
timestamp_ms: Event generation time in milliseconds (Unix timestamp)- Format: 13-digit number (e.g.,
1677721600000) - Location: Present in all event and response wrappers
Data Payload Timestamps:
timestamp: Data-specific timestamp, usually in millisecondscreation_timestamp: Order/request creation time in seconds (Unix timestamp)- Format: Varies by field type:
timestamp: 13-digit number (milliseconds) - e.g.,1677721600000creation_timestamp: 10-digit number (seconds) - e.g.,1677721600
Examples:
{
"kind": "event",
"type": "trade",
"timestamp_ms": 1677721600000, // Event time (milliseconds)
"data": {
"timestamp": 1677721600000, // Trade time (milliseconds)
"creation_timestamp": 1677721600 // Order creation (seconds)
}
}Important Notes:
- Always use
timestamp_msfor event processing and ordering - Parse
creation_timestampas seconds when working with order lifecycle data - All timestamps are UTC with no timezone offset
Event Data Structures
index_price Event
{
"kind": "event",
"type": "index_price",
"timestamp_ms": 1677721600000,
"data": {
"value": 45000.5,
"timestamp": 1677721600000
},
"subscription": {
"channel": "index_price",
"query": {
"pair": "BTC_USDC"
}
}
}instruments Event
Emitted when the list of available trading instruments is updated. This occurs when new instruments are added (e.g., new option expirations) or when instruments expire and are removed from the active list.
Event Data:
updated_at: Unix timestamp in milliseconds of when the instrument list was last updatedinstruments: Array of instrument names currently available for trading
Subscription Example (Single Market):
{
"type": "subscribe",
"subscriptions": [
{
"channel": "instruments",
"query": {
"market": "BTC"
}
}
]
}Subscription Example (All Markets):
{
"type": "subscribe",
"subscriptions": [
{
"channel": "instruments",
"query": {}
}
]
}Event Example:
{
"kind": "event",
"type": "instruments",
"timestamp_ms": 1677721600000,
"data": {
"updated_at": 1677721600000,
"instruments": [
"BTC_USDC-PERPETUAL",
"BTC_USDC-31OCT25-130000-C",
"BTC_USDC-31OCT25-130000-P",
"BTC_USDC-31OCT25-135000-C",
"BTC_USDC-31OCT25-135000-P",
"BTC_USDC-30NOV25-140000-C",
"BTC_USDC-30NOV25-140000-P"
]
},
"subscription": {
"channel": "instruments",
"query": {
"market": "BTC"
}
}
}Use Cases:
- Monitor when new option expirations become available
- Track when instruments expire and are removed
- Keep your trading application's instrument list synchronized with the exchange
- Receive immediate notification when new trading instruments become available
Notes:
- Initial subscription provides a snapshot of all current instruments for the specified market(s)
- Subsequent events are sent only when the instrument list changes
- The
instrumentsarray includes both options and perpetuals for the specified market - If no
marketis specified in the query, you'll receive separate events for each market (BTC, ETH, ARB)
post_order Event
Emitted when a new order is posted to the orderbook.
{
"kind": "event",
"type": "post_order",
"timestamp_ms": 1677721600000,
"data": {
"instrument_name": "BTC_USDC-31OCT25-130000-C",
"type": "good_til_cancelled",
"order_id": "unique-order-id",
"order_state": "open",
"filled_amount": 0,
"price": 2500.0,
"direction": "buy",
"post_only": true,
"maker": "0x1234567890abcdef1234567890abcdef12345678",
"taker": "0x0000000000000000000000000000000000000000",
"mmp": false,
"liquidation": false,
"creation_timestamp": 1677721600,
"chain_id": 421614,
"signature_deadline": 1677721700,
"signature": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef12",
"contracts": 1.5
},
"subscription": {
"channel": "orderbook_options",
"query": {
"instrument_name": "BTC_USDC-31OCT25-130000-C"
}
}
}cancel_order Event
Emitted when an order is cancelled.
{
"kind": "event",
"type": "cancel_order",
"timestamp_ms": 1677721600000,
"data": {
"instrument_name": "BTC_USDC-31OCT25-130000-C",
"type": "good_til_cancelled",
"order_id": "unique-order-id",
"order_state": "open",
"filled_amount": 0,
"price": 2640.0,
"direction": "buy",
"post_only": false,
"maker": "0x39ff2600293a6746140c3caaa51bcd62dcb06cbf",
"taker": "0x0000000000000000000000000000000000000000",
"mmp": false,
"liquidation": false,
"creation_timestamp": 1677721600,
"chain_id": 421614,
"signature_deadline": 1677721700,
"signature": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef12",
"contracts": 4
},
"subscription": {
"channel": "orderbook_options",
"query": {
"instrument_name": "BTC_USDC-31OCT25-130000-C"
}
}
}trade Event
{
"kind": "event",
"type": "trade",
"timestamp_ms": 1677721600000,
"data": {
"margin_account_id": 12345,
"smart_account_address": "0x1234567890abcdef1234567890abcdef12345678",
"trade_id": "1234567890abcdef1234567890abcdef",
"order_id": "abcdef1234567890abcdef1234567890",
"user_id": "trader_001",
"size": 1.5,
"order_size": 2.0,
"filled_amount": 1.5,
"timestamp": 1677721600000,
"direction": "buy",
"index_price": 45000.5,
"instrument_name": "BTC_USDC-31OCT25-130000-C",
"average_price": 2500.25,
"limit_price": 2505.0,
"liquidation": false,
"iv": 0.65,
"taker_fee": 0.0002,
"maker_fee": 0.0001,
"systemic_risk_fee": 0.0001,
"liquidation_fee": 0,
"realised_funding": 0
},
"subscription": {
"channel": "trade",
"query": {
"account": "0x1234567890abcdef1234567890abcdef12345678"
}
}
}Note: The order_size field indicates the total size of the original order, while filled_amount shows the cumulative amount filled so far. This allows tracking partial fills where filled_amount may be less than order_size.
deposit Event
{
"kind": "event",
"type": "deposit",
"timestamp_ms": 1677721600000,
"data": {
"margin_account_id": 12345,
"smart_account_address": "0x1234567890abcdef1234567890abcdef12345678",
"amount": 10000,
"action_seq": "abc123...",
"action": "deposit",
"symbol": "USDC"
},
"subscription": {
"channel": "transfer",
"query": {
"account": "0x1234567890abcdef1234567890abcdef12345678"
}
}
}withdrawal Event
{
"kind": "event",
"type": "withdrawal",
"timestamp_ms": 1677721600000,
"data": {
"margin_account_id": 12345,
"smart_account_address": "0x1234567890abcdef1234567890abcdef12345678",
"amount": 5000,
"action_seq": "xyz789...",
"action": "withdrawal",
"symbol": "USDC"
},
"subscription": {
"channel": "transfer",
"query": {
"account": "0x1234567890abcdef1234567890abcdef12345678"
}
}
}funding Event
{
"kind": "event",
"type": "funding",
"timestamp_ms": 1677721600000,
"data": {
"instrument_name": "BTC_USDC-PERPETUAL",
"timestamp": 1677721600000,
"spot_price": 45000.5,
"perp_price": 45010.0,
"funding_rate": 0.0001
},
"subscription": {
"channel": "funding",
"query": {
"instrument_name": "BTC_USDC-PERPETUAL"
}
}
}
Learn More: For detailed information about funding rate calculations and payment mechanics, see the Funding Rate Specification.
interest_rate Event
{
"kind": "event",
"type": "interest_rate",
"timestamp_ms": 1677721600000,
"data": {
"sid": "INTEREST-BTC_USDC",
"v": 0.05,
"timestamp": 1677721600000
},
"subscription": {
"channel": "interest_rate",
"query": {
"pair": "BTC_USDC"
}
}
}svi Event
{
"kind": "event",
"type": "svi",
"timestamp_ms": 1677721600000,
"data": {
"sid": "SVI-BTC_USDC-31OCT25",
"alpha": 0.1,
"beta": 0.2,
"rho": -0.3,
"m": 0.0,
"sigma": 0.4,
"timestamp": 1677721600000
},
"subscription": {
"channel": "iv",
"query": {
"pair": "BTC_USDC",
"maturity": "31OCT25"
}
}
}rfq_request Event
Emitted when a new RFQ request is created. RFQ requests can contain multiple instruments to create option combinations.
{
"kind": "event",
"type": "rfq_request",
"timestamp_ms": 1751468186897,
"data": {
"taker": "0x1234567890abcdef1234567890abcdef12345678",
"rfq_orders": [
{
"instrument_name": "BTC_USDC-31OCT25-130000-C",
"contracts": 25,
"direction": "buy"
},
{
"instrument_name": "BTC_USDC-31OCT25-108000-C",
"contracts": 30,
"direction": "sell"
}
],
"order_id": "863509b1a865fa3f502a9f6784122dd7"
},
"subscription": {
"channel": "rfq",
"query": {}
}
}Data Structure Notes:
taker: Address of the account requesting quotesrfq_orders: Array of market orders for multiple instrumentsorder_id: 32-character hexadecimal identifier for the RFQ request- Multiple instruments enable complex option strategies (spreads, straddles, etc.)
rfq_post_response Event
Emitted when a market maker posts a new response to an RFQ request with pricing. Each fill represents a market order paired with limit orders that would satisfy it.
{
"kind": "event",
"type": "rfq_post_response",
"timestamp_ms": 1751468277275,
"data": {
"fills": [
[
{
"direction": "buy",
"instrument_name": "BTC_USDC-31OCT25-130000-C",
"contracts": 25
},
[
{
"direction": "buy",
"instrument_name": "BTC_USDC-31OCT25-130000-C",
"type": "good_til_cancelled",
"contracts": 25,
"price": 132500,
"post_only": true,
"mmp": false,
"liquidation": false,
"taker": "0x1234567890AbcdEF1234567890aBcdef12345678",
"maker": "0x0987654321098765432109876543210987654321",
"signature": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef12",
"signature_deadline": 1735689600,
"order_id": "863509b1a865fa3f502a9f6784122dd7",
"order_state": "open",
"filled_amount": 0,
"creation_timestamp": 1751468277,
"chain_id": 421614,
"to_be_filled": 25
}
]
],
[
{
"instrument_name": "BTC_USDC-31OCT25-108000-C",
"contracts": 30,
"direction": "sell"
},
[
{
"instrument_name": "BTC_USDC-31OCT25-108000-C",
"type": "good_til_cancelled",
"contracts": 30,
"direction": "sell",
"price": 150000,
"post_only": true,
"mmp": false,
"liquidation": false,
"taker": "0x1234567890AbcdEF1234567890aBcdef12345678",
"maker": "0x0987654321098765432109876543210987654321",
"signature": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef12",
"signature_deadline": 1735689600,
"order_id": "863509b1a865fa3f502a9f6784122dd7",
"order_state": "open",
"filled_amount": 0,
"creation_timestamp": 1751468277,
"chain_id": 421614,
"to_be_filled": 30
}
]
]
],
"order_id": "863509b1a865fa3f502a9f6784122dd7",
"taker": "0x1234567890abcdef1234567890abcdef12345678",
"maker": "0x0987654321098765432109876543210987654321",
"response_id": "dc937919bb5dac8f50a82ecb16a01719"
},
"subscription": {
"channel": "rfq",
"query": {}
}
}Data Structure Notes:
fills: Array of [MarketOrder, LimitOrder[]] pairs for each instrument in the RFQorder_id: References the original RFQ request IDresponse_id: Unique identifier for this specific responsetakerandmaker: Account addresses for both parties- Each limit order includes complete trading details:
signature: Cryptographic signature for order validity (see EIP-712 Signatures Guide)signature_deadline: Unix timestamp when signature expiresorder_state: Current state ("open", "filled", "cancelled")filled_amount: Amount already filled (0 for new orders)creation_timestamp: When the order was createdchain_id: Blockchain network identifierto_be_filled: Amount that needs to be filled
- Multi-instrument responses enable atomic execution of complex option strategies
rfq_cancel_response Event
Emitted when a market maker cancels a previously posted RFQ response. This occurs when the maker withdraws their quote before it has been filled.
{
"kind": "event",
"type": "rfq_cancel_response",
"timestamp_ms": 1751468300000,
"data": {
"fills": [],
"order_id": "863509b1a865fa3f502a9f6784122dd7",
"taker": "0x1234567890AbcdEF1234567890aBcdef12345678",
"maker": "0x0987654321098765432109876543210987654321",
"response_id": "dc937919bb5dac8f50a82ecb16a01719"
},
"subscription": {
"channel": "rfq",
"query": {}
}
}Data Structure Notes:
fills: Empty array for cancelled responsesorder_id: References the original RFQ request IDresponse_id: Unique identifier of the cancelled responsetakerandmaker: Account addresses for both parties- Cancellation removes the response from the orderbook, preventing execution
- Only the maker who posted the response can cancel it
account_state Event
{
"kind": "event",
"type": "account_state",
"timestamp_ms": 1677721600000,
"data": {
"timestamp": 1677721600000,
"margin_account": 12345,
"pair": "BTC_USDC",
"im": 5000.0,
"mm": 2500.0,
"matrix_risk": 1200.0,
"delta_risk": 800.0,
"roll_risk": 300.0,
"unrealised_pnl": 1500.0,
"equity": 15000.0,
"portfolio_greeks": {
"delta": 0.45,
"gamma": 0.0012,
"vega": 125.3,
"theta": -45.5,
"rho": 85.2
},
"positions": []
},
"subscription": {
"channel": "account_state",
"query": {
"account": "0x1234567890abcdef1234567890abcdef12345678"
}
}
}account_liquidation Event
⚠️ NOTE: This event type is currently not available. While you can subscribe to the
account_liquidationchannel, no events will be delivered at this time. The event structure will be documented when this feature becomes available.
bankruptcy Event
{
"kind": "event",
"type": "bankruptcy",
"timestamp_ms": 1677721600000,
"data": {
"marginAccountId": "550e8400-e29b-41d4-a716-446655440000",
"market": "ETH_USDC"
},
"subscription": {
"channel": "bankruptcy",
"query": {
"market": "ETH_USDC"
}
}
}Data Fields:
marginAccountId: Unique identifier of the margin account that went bankrupt (UUID format)market: Trading pair where bankruptcy occurred (e.g., "BTC_USDC", "ETH_USDC")
Note: Bankruptcy events indicate that an account's equity has become negative, typically after unsuccessful liquidation attempts. This represents a loss absorbed by the insurance fund.
mmp_triggered Event
{
"kind": "event",
"type": "mmp_triggered",
"timestamp_ms": 1677721600000,
"data": {
"smart_account_address": "0xabCDEF1234567890ABcDEF1234567890aBCDeF12",
"pair_symbol": "ETH_USDC",
"frozen_until": 1677721660000,
"frozen_duration_seconds": 60
},
"subscription": {
"channel": "mmp",
"query": {
"smart_account_address": "0xabCDEF1234567890ABcDEF1234567890aBCDeF12"
}
}
}Data Fields:
smart_account_address: Address of the smart account that triggered MMPpair_symbol: Trading pair where MMP was triggered (e.g., "BTC_USDC", "ETH_USDC")frozen_until(optional): Unix timestamp in milliseconds when the freeze expiresfrozen_duration_seconds(optional): Duration of the freeze in seconds
Note: MMP (Market Maker Protection) is triggered when an account exceeds configured risk thresholds. During the freeze period, the account cannot place new orders. This protects market makers from excessive exposure during volatile market conditions.
Common Use Cases
Monitor Index Prices
Subscribe to real-time price updates for trading pairs.
import WebSocket from 'ws';
interface AuthMessage {
type: 'auth';
api_key: string;
}
interface SubscribeMessage {
type: 'subscribe';
subscriptions: Array<{
channel: string;
query: Record<string, any>;
}>;
}
interface ResponseMessage {
type: string;
success: boolean;
error?: string;
event?: string;
data?: any;
}
const wsUrl = 'wss://staging.kyan.sh/ws';
const API_KEY = 'your-api-key-here';
const ws = new WebSocket(wsUrl);
// First authenticate
ws.onopen = () => {
console.log('WebSocket connection open');
const authMessage: AuthMessage = {
type: 'auth',
api_key: API_KEY,
};
ws.send(JSON.stringify(authMessage));
};
// Handle authentication response and subscribe to index prices
ws.onmessage = (event) => {
const message = JSON.parse(event.data.toString()) as ResponseMessage;
if (message.type === 'auth' && message.success) {
console.log('Authentication successful');
// Subscribe to BTC/USDC index price updates
const subscribeMessage: SubscribeMessage = {
type: 'subscribe',
subscriptions: [
{
channel: 'index_price',
query: {
pair: 'BTC_USDC',
},
},
],
};
ws.send(JSON.stringify(subscribeMessage));
// Update the onmessage handler to process events
ws.onmessage = handleMessages;
} else if (message.type === 'auth' && !message.success) {
console.error('Authentication failed:', message.error);
}
};
// Process incoming messages after subscription
function handleMessages(event: WebSocket.MessageEvent) {
const message = JSON.parse(event.data.toString()) as ResponseMessage;
// Handle subscription confirmation
if (message.type === 'subscribe' && message.success) {
console.log('Successfully subscribed to channels');
}
// Handle index price events
if (message.event === 'index_price') {
console.log('New index price:', message.data);
}
}Stream Orderbook Updates
Subscribe to orderbook updates for options or perpetuals.
import WebSocket from 'ws';
const wsUrl = 'wss://staging.kyan.sh/ws';
const API_KEY = 'your-api-key-here';
const ws = new WebSocket(wsUrl);
// First authenticate
ws.onopen = () => {
console.log('WebSocket connection open');
const authMessage = {
type: 'auth',
api_key: API_KEY,
};
ws.send(JSON.stringify(authMessage));
};
// Handle authentication response and subscribe to orderbook
ws.onmessage = (event) => {
const message = JSON.parse(event.data.toString());
if (message.type === 'auth' && message.success) {
console.log('Authentication successful');
// Subscribe to BTC/USDC option orderbook
const subscribeMessage = {
type: 'subscribe',
subscriptions: [
{
channel: 'orderbook_options',
query: {
instrument_name: 'BTC_USDC-31OCT25-130000-C',
},
},
],
};
ws.send(JSON.stringify(subscribeMessage));
// Update the onmessage handler to process events
ws.onmessage = handleMessages;
} else if (message.type === 'auth' && !message.success) {
console.error('Authentication failed:', message.error);
}
};
// Process incoming messages after subscription
function handleMessages(event: WebSocket.MessageEvent) {
const message = JSON.parse(event.data.toString());
// Handle subscription confirmation
if (message.type === 'subscribe' && message.success) {
console.log('Successfully subscribed to orderbook');
}
// Handle orderbook events
if (message.event === 'post_order') {
console.log('New order posted:', message.data);
} else if (message.event === 'cancel_order') {
console.log('Order cancelled:', message.data);
}
}Monitor Account Activity
Track trades, transfers, and other account activities.
import WebSocket from 'ws';
const wsUrl = 'wss://staging.kyan.sh/ws';
const API_KEY = 'your-api-key-here';
const ACCOUNT_ADDRESS = '0x1234567890abcdef1234567890abcdef12345678';
const ws = new WebSocket(wsUrl);
// First authenticate
ws.onopen = () => {
console.log('WebSocket connection open');
const authMessage = {
type: 'auth',
api_key: API_KEY,
};
ws.send(JSON.stringify(authMessage));
};
// Handle authentication response and subscribe to account activity
ws.onmessage = (event) => {
const message = JSON.parse(event.data.toString());
if (message.type === 'auth' && message.success) {
console.log('Authentication successful');
// Subscribe to account trades and transfers
const subscribeMessage = {
type: 'subscribe',
subscriptions: [
{
channel: 'trade',
query: {
account: ACCOUNT_ADDRESS,
},
},
{
channel: 'transfer',
query: {
account: ACCOUNT_ADDRESS,
},
},
],
};
ws.send(JSON.stringify(subscribeMessage));
// Update the onmessage handler to process events
ws.onmessage = handleMessages;
} else if (message.type === 'auth' && !message.success) {
console.error('Authentication failed:', message.error);
}
};
// Process incoming messages after subscription
function handleMessages(event: WebSocket.MessageEvent) {
const message = JSON.parse(event.data.toString());
// Handle subscription confirmation
if (message.type === 'subscribe' && message.success) {
console.log('Successfully subscribed to account activity');
}
// Handle account events
if (message.event === 'trade') {
console.log('New trade:', message.data);
} else if (message.event === 'deposit') {
console.log('New deposit:', message.data);
} else if (message.event === 'withdrawal') {
console.log('New withdrawal:', message.data);
}
}Error Handling
Error responses follow this format:
{
"kind": "response",
"type": "error",
"id": "optional-request-id",
"timestamp_ms": 1677721600000,
"success": false,
"error": "Description of the error"
}Common error scenarios:
-
Authentication failures:
-
"Failed to validate api key" - Generic validation failure
-
"Not Authorized" - Not authenticated
-
"Session already authorized" - Already authenticated
-
Specific API key error codes: "NOT_FOUND", "FORBIDDEN", "KEY_USAGE_EXCEEDED", "RATELIMITED"
Learn More: For API key troubleshooting and error resolution, see the API Key Guide. -
-
Invalid subscriptions:
- "Invalid subscription parameters"
- "Invalid query for [CHANNEL] channel"
-
Invalid messages:
- "invalid [MESSAGE_TYPE] payload [error_details]"
Handling Specific Errors
interface ErrorResponse {
type: string;
timestampMs: number;
success: boolean;
error: string;
}
function handleWebSocketError(error: ErrorResponse): void {
// Log the error
console.error(`WebSocket error: ${error.error}`);
// Handle specific authentication errors
if (
error.error.includes('Failed to validate api key') ||
error.error === 'NOT_FOUND' ||
error.error === 'FORBIDDEN' ||
error.error === 'KEY_USAGE_EXCEEDED'
) {
// API key issues - might need to refresh
console.error('API key error:', error.error);
// Trigger API key refresh or user notification
} else if (error.error === 'RATELIMITED') {
// Rate limiting - implement backoff
console.warn('Rate limited. Implementing backoff...');
// Slow down request rate
} else if (error.error === 'Not Authorized') {
// Not authenticated - try to authenticate
console.warn('Not authenticated. Attempting to authenticate...');
// Attempt to authenticate
} else if (error.error === 'Session already authorized') {
// Already authenticated - no action needed
console.info('Session already authenticated');
} else if (error.error.includes('Invalid subscription parameters')) {
// Invalid subscription - check subscription format
console.error('Invalid subscription:', error.error);
// Fix subscription parameters
} else {
// Generic error handling
console.error('Unhandled WebSocket error:', error.error);
}
}Rate Limiting
Overview
The WebSocket API implements per-session rate limiting to ensure fair usage and system stability. Rate limits are applied to specific message types that query market data.
Rate Limited Messages
The following WebSocket message types have rate limits applied:
get_ob_state_by_instruments: 15 requests per minute per sessionget_ob_state_by_market: 15 requests per minute per session
Rate Limit Implementation
Rate limits use a sliding window algorithm:
- Window: 60 seconds (1 minute)
- Limit: 15 requests per session per window
- Scope: Per WebSocket session (each WebSocket connection has its own independent rate limit)
Rate Limit Responses
When you exceed the rate limit, you'll receive an error response:
{
"kind": "response",
"type": "get_ob_state_by_instruments",
"id": "your-request-id",
"timestamp_ms": 1677721600000,
"success": false,
"error": "Rate limit exceeded: 15 requests per 60 seconds. Reset in 23 seconds. Remaining: 0"
}Error Response Fields:
error: Describes the rate limit violation and reset time- The error message includes:
- Current rate limit (e.g., "15 requests per 60 seconds")
- Time until reset (e.g., "Reset in 23 seconds")
- Remaining requests in current window (e.g., "Remaining: 0")
Handling Rate Limits
Best Practices:
- Monitor your request rate: Stay within 15 requests per minute for rate-limited endpoints
- Implement exponential backoff: When rate limited, wait before retrying
- Parse error messages: Extract reset time and remaining requests from error responses
- Use subscriptions: Instead of repeatedly polling
get_ob_state_*, use channel subscriptions for real-time updates
Example Rate Limit Handler:
function handleRateLimitedResponse(response: any) {
if (!response.success && response.error?.includes('Rate limit exceeded')) {
// Extract reset time from error message
const resetMatch = response.error.match(/Reset in (\d+) seconds/);
const resetSeconds = resetMatch ? parseInt(resetMatch[1]) : 60;
console.warn(
`Rate limited. Waiting ${resetSeconds} seconds before retry...`,
);
// Implement backoff strategy
setTimeout(() => {
console.log('Rate limit window reset. Safe to retry.');
// Retry your request here
}, resetSeconds * 1000);
}
}Alternative Approach - Use Subscriptions:
Instead of polling market data with get_ob_state_*, consider subscribing to real-time channels:
// Instead of repeatedly calling get_ob_state_by_instruments
// Use subscription for real-time updates (no rate limit)
ws.send(
JSON.stringify({
type: 'subscribe',
channels: ['orders', 'trades', 'account'],
query: { pair: 'ETH_USDC' },
}),
);Connection Management
Connection Lifecycle and Session Duration
Important: WebSocket client sessions have a maximum duration of 1 hour. For long-running applications, you must implement reconnection logic to maintain persistent data streams. There is no heartbeat/ping-pong mechanism built into the server. It is your responsibility as the client to handle reconnection for long-living sessions.
Reconnection Strategy Example
class WebSocketClient {
private ws: WebSocket | null = null;
private readonly url: string;
private readonly apiKey: string;
private reconnectAttempts: number = 0;
private maxReconnectAttempts: number = 5;
private reconnectInterval: number = 1000; // Start with 1 second
private subscriptions: Array<{
channel: string;
query: Record<string, any>;
}> = [];
private isAuthenticated: boolean = false;
constructor(url: string, apiKey: string) {
this.url = url;
this.apiKey = apiKey;
}
connect(): void {
this.ws = new WebSocket(this.url);
this.ws.onopen = () => {
console.log('Connected to WebSocket');
this.reconnectAttempts = 0;
this.reconnectInterval = 1000; // Reset backoff
this.authenticate();
};
this.ws.onclose = (event) => {
console.log(`Connection closed: ${event.code} - ${event.reason}`);
this.isAuthenticated = false;
// Implement exponential backoff reconnection
if (this.reconnectAttempts < this.maxReconnectAttempts) {
console.log(
`Attempting to reconnect in ${this.reconnectInterval}ms (attempt ${this.reconnectAttempts + 1}/${this.maxReconnectAttempts})`,
);
setTimeout(() => {
this.reconnectAttempts++;
this.reconnectInterval = Math.min(this.reconnectInterval * 2, 30000); // Cap at 30 seconds
this.connect();
}, this.reconnectInterval);
} else {
console.error(
'Max reconnection attempts reached. Manual reconnection required.',
);
}
};
this.ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
this.ws.onmessage = (event) => {
const message = JSON.parse(event.data);
this.handleMessage(message);
};
}
private authenticate(): void {
if (!this.ws) return;
const authMessage = {
type: 'auth',
api_key: this.apiKey,
};
this.ws.send(JSON.stringify(authMessage));
}
private handleMessage(message: any): void {
if (message.type === 'auth' && message.success) {
console.log('Authenticated successfully');
this.isAuthenticated = true;
// Resubscribe to previous subscriptions after reconnection
if (this.subscriptions.length > 0) {
this.resubscribe();
}
} else if (message.type === 'auth' && !message.success) {
console.error('Authentication failed:', message.error);
} else if (message.event) {
// Handle events
console.log(`Event received: ${message.event}`, message.data);
}
}
subscribe(
subscriptions: Array<{ channel: string; query: Record<string, any> }>,
): void {
if (!this.isAuthenticated) {
console.warn('Cannot subscribe: not authenticated');
return;
}
// Store subscriptions for reconnection
this.subscriptions = [...this.subscriptions, ...subscriptions];
const subscribeMessage = {
type: 'subscribe',
subscriptions: subscriptions,
};
this.ws?.send(JSON.stringify(subscribeMessage));
}
private resubscribe(): void {
if (this.subscriptions.length > 0) {
console.log('Resubscribing to channels after reconnection...');
const subscribeMessage = {
type: 'subscribe',
subscriptions: this.subscriptions,
};
this.ws?.send(JSON.stringify(subscribeMessage));
}
}
disconnect(): void {
if (this.ws) {
// Prevent reconnection attempts
this.reconnectAttempts = this.maxReconnectAttempts;
// Send logout message
const logoutMessage = { type: 'logout' };
this.ws.send(JSON.stringify(logoutMessage));
this.ws.close();
this.ws = null;
this.isAuthenticated = false;
}
}
// Manual reconnection method
forceReconnect(): void {
this.reconnectAttempts = 0;
this.reconnectInterval = 1000;
if (this.ws) {
this.ws.close();
}
this.connect();
}
}
// Usage with proper error handling
const client = new WebSocketClient('wss://staging.kyan.sh/ws', 'your-api-key');
client.connect();
// Subscribe to channels after connection
setTimeout(() => {
client.subscribe([
{
channel: 'index_price',
query: { pair: 'BTC_USDC' },
},
]);
}, 2000); // Wait for authenticationBest Practices
-
Connection Management:
- Always authenticate immediately after connecting
- Implement robust reconnection logic with exponential backoff - connections timeout after 1 hour.
- Handle connection drops gracefully and automatically
- Store subscription state for seamless reconnection
- Monitor connection health and implement manual reconnection triggers
-
Subscription Management:
- Keep track of active subscriptions for reconnection
- Automatically resubscribe after successful reconnection
- Unsubscribe from channels you no longer need to reduce bandwidth
- Validate subscription success before considering them active
-
Error Handling:
- Implement comprehensive error handling for all message types
- Log errors for debugging and monitoring
- Handle rate limiting with exponential backoff (see Rate Limiting section for details)
- Differentiate between recoverable and non-recoverable errors
-
Performance:
- Batch subscriptions when possible to reduce overhead
- Process messages efficiently to avoid blocking
- Consider using worker threads for heavy message processing
- Implement message queuing during reconnection periods
-
Security:
- Never expose API keys in client-side code
- Use secure WebSocket connections (wss://) only
- Validate all incoming data before processing
- Implement proper authentication state management
-
Reliability:
- Plan for regular disconnections - client sessions expire after 1 hour
- Implement connection health monitoring and automatic reconnection for long-running applications
- Use proper logging to track connection lifecycle events
- Test reconnection logic thoroughly in development
Updated 5 days ago
