Skip to main content

WebSocket API General Info

Basic information and connection guide for ZTDX WebSocket API.

Base URL​

NetworkWebSocket URL
Mainnetwss://api.ztdx.io/ws
Testnet (Sepolia)wss://api-sepolia.ztdx.io/ws

Endpoints​

PathDescription
/wsInternal market data (orderbook, trades, ticker, kline, positions, orders, balance)
/ws/internalSame as /ws — explicit internal data endpoint
/ws/externalExternal price feed (Hyperliquid proxy)

Connection​

  • WebSocket connections use standard WebSocket protocol (RFC 6455).
  • All messages are sent and received as JSON text frames.
  • The server responds to WebSocket Ping frames with Pong frames automatically.
  • A single WebSocket connection can subscribe to multiple channels simultaneously.

Connection Example​

const ws = new WebSocket('wss://api.ztdx.io/ws');

ws.onopen = () => {
console.log('Connected');
// Subscribe to a public channel
ws.send(JSON.stringify({
type: 'subscribe',
channel: 'ticker:BTCUSDT'
}));
};

ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Received:', data);
};

Message Format​

Client → Server Messages​

All client messages must include a type field.

TypeDescriptionAuth Required
authAuthenticate via EIP-712 signature, JWT, or fapi listenKey—
auth_tokenAuthenticate via JWT token—
subscribeSubscribe to a channelPrivate channels only
unsubscribeUnsubscribe from a channelNo
pingHeartbeat pingNo

Server → Client Messages​

All server messages include a type field.

TypeDescription
auth_resultAuthentication result
subscribedSubscription confirmation
unsubscribedUnsubscription confirmation
tradeTrade update
orderbookOrder book snapshot/update
tickerTicker data
klineK-line/candlestick data
kline_snapshotK-line initial snapshot on subscribe
positionPosition update (private)
orderOrder update (private)
balanceBalance update (private)
errorError message
pongHeartbeat pong

Authentication​

Private channels (positions, orders, balance) require authentication before subscribing. Three authentication methods are supported:

Method 1: EIP-712 Signature​

{
"type": "auth",
"address": "0xYourWalletAddress",
"signature": "0x...",
"timestamp": 1711000000
}
  • address — Ethereum wallet address.
  • signature — EIP-712 typed data signature.
  • timestamp — UNIX timestamp in seconds. Must be within 5 minutes of server time.

Method 2: JWT Token​

{
"type": "auth",
"token": "eyJhbGciOiJIUzI1..."
}

Or use the dedicated auth_token type:

{
"type": "auth_token",
"token": "eyJhbGciOiJIUzI1..."
}

Method 3: fapi listenKey​

For HMAC-authenticated bots (Binance fapi clients), obtain a listenKey via POST /fapi/v1/listenKey and pass it on the WebSocket:

{
"type": "auth",
"listenKey": "a1b2c3d4e5f6..."
}

The server resolves the listenKey to its owning user_address (Redis lookup). The listenKey has a TTL of 3600 seconds (60 minutes) and is refreshed only when it is used to authenticate — an open WebSocket connection does not keep it alive by itself. Call PUT /fapi/v1/listenKey periodically (e.g. every 30 minutes) to extend the TTL; once expired, re-issue via POST /fapi/v1/listenKey.

Method 4: Token with Subscribe​

You can also pass a token field directly in a subscribe message. The server will auto-authenticate before processing the subscription:

{
"type": "subscribe",
"channel": "positions",
"token": "eyJhbGciOiJIUzI1..."
}

Auth Response​

{
"type": "auth_result",
"success": true,
"message": null
}

On failure:

{
"type": "auth_result",
"success": false,
"message": "Invalid or expired token"
}

Channels​

Public Channels​

Public channels do not require authentication.

ChannelFormatDescription
Orderbookorderbook:{symbol}Real-time order book depth (top 30 levels)
Tradestrades:{symbol}Real-time trade stream
Tickerticker:{symbol}Market ticker (every 2 seconds)
K-linekline:{symbol}:{interval}Candlestick data

Private Channels​

Private channels require authentication.

ChannelFormatDescription
PositionspositionsUser position updates (every 5 seconds)
OrdersordersUser order updates (real-time + every 5 seconds)
BalancebalanceUser balance updates (every 5 seconds)

Symbol Format​

Symbols support multiple input formats and are automatically normalized:

InputNormalized
BTCUSDTBTCUSDT
btcusdtBTCUSDT
BTC-USDBTCUSDT
BTC-USDTBTCUSDT
BTC/USDBTCUSDT
BTC_USDBTCUSDT

K-line Intervals​

Supported intervals for kline:{symbol}:{interval}:

1m, 5m, 15m, 1h, 4h, 1d, 1w, 1M

Subscribing with any other interval is acknowledged with a subscribed message but produces no data pushes.

Subscribe / Unsubscribe​

Subscribe​

{
"type": "subscribe",
"channel": "orderbook:BTCUSDT"
}

Response:

{
"type": "subscribed",
"channel": "orderbook:BTCUSDT"
}

Upon subscribing to orderbook, ticker, positions, orders, or balance channels, the server immediately sends a snapshot of the current data.

Unsubscribe​

{
"type": "unsubscribe",
"channel": "orderbook:BTCUSDT"
}

Response:

{
"type": "unsubscribed",
"channel": "orderbook:BTCUSDT"
}

Heartbeat / Keep-Alive​

Send a ping message to keep the connection alive:

{
"type": "ping"
}

Response:

{
"type": "pong"
}

The server also responds to standard WebSocket Ping frames with Pong frames.

Data Payloads​

Trade​

{
"type": "trade",
"id": "1711000000000-a1b2c3d4",
"symbol": "BTCUSDT",
"price": "65432.10000",
"amount": "0.5",
"side": "buy",
"timestamp": 1711000000000
}

Orderbook​

{
"type": "orderbook",
"symbol": "BTCUSDT",
"bids": [
{ "price": "65430.00", "size": "1.2" },
{ "price": "65429.50", "size": "0.8" }
],
"asks": [
{ "price": "65431.00", "size": "0.5" },
{ "price": "65431.50", "size": "1.0" }
],
"timestamp": 1711000000000
}
  • Orderbook updates are pushed every 500ms.
  • Top 30 levels on each side.

Ticker​

{
"type": "ticker",
"symbol": "BTCUSDT",
"last_price": "65432.10",
"mark_price": "65433.00",
"index_price": "65431.50",
"price_change_24h": "1200.50",
"price_change_percent_24h": "1.87",
"high_24h": "66000.00",
"low_24h": "64000.00",
"volume_24h": "12345.6789",
"volume_24h_usd": "807654321.00",
"open_interest_long": "5000.0000",
"open_interest_short": "4200.0000",
"open_interest_long_percent": "54",
"open_interest_short_percent": "46",
"available_liquidity_long": "1500000.00",
"available_liquidity_short": "1200000.00",
"funding_rate_long_1h": "+0.0050%",
"funding_rate_short_1h": "-0.0050%"
}
  • Ticker updates are pushed every 2 seconds.

K-line​

{
"type": "kline",
"channel": "kline:BTCUSDT:5m",
"data": {
"time": 1711000000,
"open": "65400.00",
"high": "65500.00",
"low": "65350.00",
"close": "65432.10",
"volume": "123.456",
"quote_volume": "8074000.00",
"trade_count": 542,
"is_final": false
}
}
  • time: candle open time as a UNIX timestamp in seconds (unlike most other timestamps in this API, which are in milliseconds).
  • is_final: true when the candlestick period has closed and the candle is complete.

Position (Private)​

{
"type": "position",
"id": "pos-uuid-1234",
"symbol": "BTCUSDT",
"side": "long",
"size": "0.5",
"entry_price": "65000.00",
"mark_price": "65432.10",
"liquidation_price": "62000.00",
"unrealized_pnl": "216.05",
"leverage": 10,
"margin": "3250.00",
"updated_at": 1711000000000,
"event": "update"
}

Order (Private)​

{
"type": "order",
"id": "order-uuid-5678",
"symbol": "BTCUSDT",
"side": "buy",
"order_type": "limit",
"price": "64000.00",
"amount": "0.5",
"filled_amount": "0.0",
"status": "open",
"updated_at": 1711000000000,
"event": "created"
}

Order updates are delivered in real-time (pushed immediately when an order is created, filled, or cancelled) and also polled every 5 seconds.

Balance (Private)​

{
"type": "balance",
"token": "USDT",
"symbol": "USDT",
"available": "10000.00",
"frozen": "3250.00",
"total": "13250.00"
}

Error Messages​

{
"type": "error",
"code": "AUTH_REQUIRED",
"message": "Authentication required for private channels"
}
Error CodeDescription
INVALID_MESSAGEFailed to parse the client message
INVALID_CHANNELUnknown channel name in a subscribe request
AUTH_REQUIREDAuthentication required for private channel
FORBIDDENAttempt to subscribe to another user's private channel
HL_CONNECTION_FAILEDFailed to connect to external price feed (external endpoint only)
HL_ERRORExternal price feed connection error (external endpoint only)
HL_DISCONNECTEDExternal price feed disconnected (external endpoint only)

Update Intervals​

Data TypePush Interval
TradesReal-time (matching engine broadcast)
OrderbookEvery 500ms
TickerEvery 2 seconds
K-lineReal-time (on candle update)
PositionsEvery 5 seconds
OrdersReal-time + every 5 seconds
BalanceEvery 5 seconds

Full Example​

const ws = new WebSocket('wss://api.ztdx.io/ws');

ws.onopen = () => {
// 1. Authenticate
ws.send(JSON.stringify({
type: 'auth_token',
token: 'your-jwt-token'
}));
};

ws.onmessage = (event) => {
const msg = JSON.parse(event.data);

switch (msg.type) {
case 'auth_result':
if (msg.success) {
// 2. Subscribe to channels after auth
ws.send(JSON.stringify({ type: 'subscribe', channel: 'ticker:BTCUSDT' }));
ws.send(JSON.stringify({ type: 'subscribe', channel: 'orderbook:BTCUSDT' }));
ws.send(JSON.stringify({ type: 'subscribe', channel: 'trades:BTCUSDT' }));
ws.send(JSON.stringify({ type: 'subscribe', channel: 'kline:BTCUSDT:5m' }));
ws.send(JSON.stringify({ type: 'subscribe', channel: 'positions' }));
ws.send(JSON.stringify({ type: 'subscribe', channel: 'orders' }));
ws.send(JSON.stringify({ type: 'subscribe', channel: 'balance' }));
}
break;
case 'ticker':
console.log(`${msg.symbol}: ${msg.last_price} (${msg.price_change_percent_24h}%)`);
break;
case 'trade':
console.log(`Trade: ${msg.side} ${msg.amount} @ ${msg.price}`);
break;
case 'orderbook':
console.log(`Orderbook: ${msg.bids.length} bids, ${msg.asks.length} asks`);
break;
case 'position':
console.log(`Position: ${msg.symbol} ${msg.side} PnL: ${msg.unrealized_pnl}`);
break;
case 'order':
console.log(`Order: ${msg.symbol} ${msg.side} ${msg.status}`);
break;
case 'error':
console.error(`Error [${msg.code}]: ${msg.message}`);
break;
}
};

// 3. Keep alive
setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'ping' }));
}
}, 30000);