Overview
The WebSocket API broadcasts three types of real-time updates: depth updates, ticker updates, and trade updates. All messages are sent as JSON-encoded strings.
Message Structure
All outgoing WebSocket messages follow this general structure:
{
stream : string ; // Channel identifier
data : {
e : string ; // Event type: "depth", "ticker", or "trade"
// ... event-specific fields
}
}
Depth Updates
Depth updates contain order book bid and ask data for an event. These updates are sent whenever the order book changes due to new orders, cancellations, or trades.
Channel identifier, typically in format "event_orderbook_{eventId}"
Event type, always "depth"
Array of bid levels [price, quantity]. Optional - only present when bids change.
Array of ask levels [price, quantity]. Optional - only present when asks change.
Event ID for this depth update
Example Message
{
"stream" : "event_orderbook_3169798" ,
"data" : {
"e" : "depth" ,
"b" : [
[ "5.5" , "100" ],
[ "5.4" , "250" ],
[ "5.3" , "180" ]
],
"a" : [
[ "5.6" , "150" ],
[ "5.7" , "200" ],
[ "5.8" , "120" ]
],
"id" : 3169798
}
}
Understanding Depth Data
Bids represent buy orders. Each entry is a tuple of [price, quantity]:
price: The price buyers are willing to pay (as string)
quantity: Total quantity available at that price (as string)
Bids are typically sorted from highest to lowest price.
Asks represent sell orders. Each entry is a tuple of [price, quantity]:
price: The price sellers are asking for (as string)
quantity: Total quantity available at that price (as string)
Asks are typically sorted from lowest to highest price.
Depth updates may contain only b (bids changed), only a (asks changed), or both. If a field is absent, it means that side of the order book hasn’t changed.
Handling Depth Updates
ws . onmessage = ( event ) => {
const message = JSON . parse ( event . data );
if ( message . data . e === 'depth' ) {
const { b : bids , a : asks , id : eventId } = message . data ;
// Update order book display
if ( bids ) {
updateBids ( eventId , bids );
}
if ( asks ) {
updateAsks ( eventId , asks );
}
}
};
function updateBids ( eventId , bids ) {
bids . forEach (([ price , quantity ]) => {
console . log ( `Event ${ eventId } - Bid: ${ quantity } @ ${ price } ` );
// Update UI with bid data
});
}
function updateAsks ( eventId , asks ) {
asks . forEach (([ price , quantity ]) => {
console . log ( `Event ${ eventId } - Ask: ${ quantity } @ ${ price } ` );
// Update UI with ask data
});
}
Ticker Updates
Ticker updates provide aggregated price and volume statistics for an event.
Channel identifier for the ticker stream
Event type, always "ticker"
Event ID for this ticker update
Example Message
{
"stream" : "ticker_3169798" ,
"data" : {
"e" : "ticker" ,
"id" : 3169798 ,
"c" : "5.55" ,
"h" : "5.80" ,
"l" : "5.20" ,
"v" : "12500" ,
"V" : "68750" ,
"s" : "EVENT_3169798"
}
}
Handling Ticker Updates
ws . onmessage = ( event ) => {
const message = JSON . parse ( event . data );
if ( message . data . e === 'ticker' ) {
const { c , h , l , v , id } = message . data ;
console . log ( `Event ${ id } Ticker:` );
console . log ( ` Current: ${ c } ` );
console . log ( ` High: ${ h } ` );
console . log ( ` Low: ${ l } ` );
console . log ( ` Volume: ${ v } ` );
// Update ticker display in UI
updateTickerDisplay ( id , { current: c , high: h , low: l , volume: v });
}
};
Trade Updates
Trade updates are broadcast when orders are matched and trades are executed.
Channel identifier for the trade stream
Event type, always "trade"
Whether the buyer is the maker (true) or taker (false)
Example Message
{
"stream" : "trade_3169798" ,
"data" : {
"e" : "trade" ,
"t" : "TRD_550e8400-e29b-41d4-a716-446655440000" ,
"m" : true ,
"p" : 5.55 ,
"q" : "100" ,
"s" : "EVENT_3169798"
}
}
Understanding Trade Data
Unique identifier for the trade. Format: TRD_{uuid}
true: The buyer placed a limit order that was matched (maker)
false: The buyer placed a market order or aggressive limit order (taker)
Makers typically pay lower fees as they provide liquidity.
The price at which the trade was executed (as number).
The quantity of contracts traded (as string).
Handling Trade Updates
ws . onmessage = ( event ) => {
const message = JSON . parse ( event . data );
if ( message . data . e === 'trade' ) {
const { t : tradeId , m : isBuyerMaker , p : price , q : quantity , s : symbol } = message . data ;
console . log ( `Trade Executed:` );
console . log ( ` ID: ${ tradeId } ` );
console . log ( ` Symbol: ${ symbol } ` );
console . log ( ` Price: ${ price } ` );
console . log ( ` Quantity: ${ quantity } ` );
console . log ( ` Buyer is Maker: ${ isBuyerMaker } ` );
// Add trade to recent trades list
addTradeToHistory ({
id: tradeId ,
price: price ,
quantity: parseFloat ( quantity ),
isBuyerMaker: isBuyerMaker ,
timestamp: Date . now ()
});
}
};
function addTradeToHistory ( trade ) {
// Add to trade history and update UI
console . log ( 'Trade added to history:' , trade );
}
Complete WebSocket Handler
Here’s a complete example that handles all message types:
TypeScript - Complete Handler
import type {
DepthUpdateMessage ,
TickerUpdateMessage ,
TradeAddedMessage ,
WsMessage
} from '@opinix/types' ;
class OpinixWebSocketClient {
private ws : WebSocket ;
private subscriptions = new Set < string >();
private orderBooks = new Map < number , any >();
private tickers = new Map < number , any >();
private trades = new Map < string , any []>();
constructor ( url : string ) {
this . ws = new WebSocket ( url );
this . setupHandlers ();
}
private setupHandlers () {
this . ws . onopen = () => {
console . log ( '✓ Connected to Opinix Trade WebSocket' );
};
this . ws . onmessage = ( event : MessageEvent ) => {
try {
const message : WsMessage = JSON . parse ( event . data );
this . handleMessage ( message );
} catch ( error ) {
console . error ( 'Failed to parse WebSocket message:' , error );
}
};
this . ws . onerror = ( error : Event ) => {
console . error ( 'WebSocket error:' , error );
};
this . ws . onclose = () => {
console . log ( '✗ Disconnected from WebSocket' );
this . reconnect ();
};
}
private handleMessage ( message : WsMessage ) {
const eventType = message . data ?. e ;
switch ( eventType ) {
case 'depth' :
this . handleDepth ( message as DepthUpdateMessage );
break ;
case 'ticker' :
this . handleTicker ( message as TickerUpdateMessage );
break ;
case 'trade' :
this . handleTrade ( message as TradeAddedMessage );
break ;
default :
console . log ( 'Unknown message type:' , message );
}
}
private handleDepth ( message : DepthUpdateMessage ) {
const { b : bids , a : asks , id } = message . data ;
console . log ( `📊 Depth update for event ${ id } ` );
// Store or update order book
let orderBook = this . orderBooks . get ( id ) || { bids: [], asks: [] };
if ( bids ) orderBook . bids = bids ;
if ( asks ) orderBook . asks = asks ;
this . orderBooks . set ( id , orderBook );
}
private handleTicker ( message : TickerUpdateMessage ) {
const { id , c , h , l , v } = message . data ;
console . log ( `📈 Ticker update for event ${ id } : ${ c } ` );
// Store ticker data
this . tickers . set ( id , { current: c , high: h , low: l , volume: v });
}
private handleTrade ( message : TradeAddedMessage ) {
const { t : tradeId , p : price , q : quantity , s : symbol } = message . data ;
console . log ( `🤝 Trade executed: ${ quantity } @ ${ price } ` );
// Store trade
const eventId = symbol . split ( '_' )[ 1 ] || symbol ;
let eventTrades = this . trades . get ( eventId ) || [];
eventTrades . unshift ({ id: tradeId , price , quantity , timestamp: Date . now () });
this . trades . set ( eventId , eventTrades . slice ( 0 , 100 )); // Keep last 100
}
private reconnect () {
setTimeout (() => {
console . log ( 'Reconnecting...' );
this . ws = new WebSocket ( 'ws://wss.opinix.trade' );
this . setupHandlers ();
// Resubscribe to previous subscriptions
if ( this . subscriptions . size > 0 ) {
this . ws . onopen = () => {
this . subscribe ( Array . from ( this . subscriptions ));
};
}
}, 3000 );
}
subscribe ( eventIds : string []) {
eventIds . forEach ( id => this . subscriptions . add ( id ));
this . ws . send ( JSON . stringify ({
method: 'subscribe_orderbook' ,
events: eventIds
}));
console . log ( `📡 Subscribed to events: ${ eventIds . join ( ', ' ) } ` );
}
unsubscribe ( eventIds : string []) {
eventIds . forEach ( id => this . subscriptions . delete ( id ));
this . ws . send ( JSON . stringify ({
method: 'unsubscribe_orderbook' ,
events: eventIds
}));
console . log ( `📴 Unsubscribed from events: ${ eventIds . join ( ', ' ) } ` );
}
close () {
this . ws . close ();
}
}
// Usage
const client = new OpinixWebSocketClient ( 'ws://wss.opinix.trade' );
// Subscribe to events when connected
client . subscribe ([ '3169798' , '3169799' ]);
// Later, unsubscribe from specific events
// client.unsubscribe(['3169799']);
Type Definitions
Based on packages/types/src/index.ts:170-209:
export type TickerUpdateMessage = {
stream : string ;
data : {
c ?: string ; // current/close price
h ?: string ; // high price
l ?: string ; // low price
v ?: string ; // volume
V ?: string ; // quote volume
s ?: string ; // symbol
id : number ; // event ID
e : "ticker" ;
};
};
export type DepthUpdateMessage = {
stream : string ;
data : {
b ?: [ string , string ][]; // bids [price, quantity]
a ?: [ string , string ][]; // asks [price, quantity]
e : "depth" ;
};
};
export type TradeAddedMessage = {
stream : string ;
data : {
e : "trade" ;
t : string ; // trade ID
m : boolean ; // is buyer maker
p : number ; // price
q : string ; // quantity
s : string ; // symbol
};
};
export type WsMessage =
| TickerUpdateMessage
| DepthUpdateMessage
| TradeAddedMessage ;
Implementation Notes
Source Code Reference:
Message type definitions: packages/types/src/index.ts:170-209
WebSocket types: services/wss/src/types/index.ts:18-28
Engine types: packages/types/src/index.ts:46-95
Message Broadcasting
WebSocket messages are broadcast via Redis pub/sub. When events occur in the matching engine:
Engine publishes message to Redis channel
WebSocket server receives message from Redis
Server broadcasts to all subscribed clients
Note the inconsistent type usage:
Depth updates: prices and quantities as strings (["5.5", "100"])
Trade updates: price as number , quantity as string
Always parse values before calculations
Next Steps
WebSocket Overview Learn about connection and subscription
Orders API Place orders that trigger these events