Subscription Topics
Available WebSocket topics and message formats
Overview
After connecting, subscribe to topics to receive real-time updates. Each topic targets a specific data stream.
Subscribe / Unsubscribe
Send a JSON message on the open WebSocket connection. Use the topics array to manage one or more topics at once:
{ "action": "subscribe", "topics": ["market:abc123:price", "user:uid123:orders"] }{ "action": "unsubscribe", "topics": ["market:abc123:price"] }Actions: subscribe | unsubscribe
You can manage multiple topics on a single connection.
Available Topics
Subscription Topics (subscription required)
All server messages arrive with "type": "data". Use the topic field to identify the stream.
| Topic | Description | Required Scope |
|---|---|---|
market:{tokenID}:book | Orderbook depth updates for a token | ws:read |
market:{marketID}:price | Price updates for a market | ws:read |
user:{userID}:orders | Your order status changes | ws:read |
user:{userID}:balance | Your balance changes | ws:read |
user:{userID}:notifications | Your notifications | ws:read |
Broadcast Topics (no subscription needed)
These are pushed to all connected clients automatically — no subscribe message required. Messages also arrive with "type": "data".
| Topic | Description |
|---|---|
event:{eventID}:score | Live score updates |
market:{marketID}:status | Market open/close/settlement status changes |
Server → Client Message Format
All server-pushed messages follow this envelope:
{
"type": "data",
"topic": "market:abc123:price",
"ts": 1712000000000,
"data": { ... }
}| Field | Description |
|---|---|
type | Message type: snapshot, data, subscribed, unsubscribed, ping, error, auth_expired |
topic | The topic this message belongs to |
ts | Server timestamp (Unix milliseconds) |
data | Payload (varies by topic) |
For topics that support snapshots, the server immediately sends a snapshot message with the current cached state after subscription, followed by data messages as updates arrive. Topics that support snapshots: market:{marketID}:price, market:{tokenID}:book, event:{eventID}:score, user:{userID}:balance. Topics that do not send an initial snapshot: market:{marketID}:status, user:{userID}:orders, user:{userID}:notifications.
Topic: market:{tokenID}:book
Real-time orderbook update whenever bids or asks change. The tokenID is the on-chain token identifier (asset ID), not the market UUID.
Subscribe
{ "action": "subscribe", "topics": ["market:0xabc123:book"] }Message
{
"type": "data",
"topic": "market:0xabc123:book",
"ts": 1712000000000,
"data": {
"asset_id": "0xabc123",
"bids": [
{ "price": "0.55", "size": "200.00" },
{ "price": "0.54", "size": "150.00" }
],
"asks": [
{ "price": "0.56", "size": "100.00" },
{ "price": "0.57", "size": "80.00" }
]
}
}Topic: market:{marketID}:price
Lightweight price tick — best bid and ask for a market.
Subscribe
{ "action": "subscribe", "topics": ["market:550e8400-e29b-41d4-a716-446655440000:price"] }Message
{
"type": "data",
"topic": "market:550e8400-e29b-41d4-a716-446655440000:price",
"ts": 1712000000001,
"data": {
"market_id": "550e8400-e29b-41d4-a716-446655440000",
"yes": { "best_bid": 0.55, "best_ask": 0.56 },
"no": { "best_bid": 0.44, "best_ask": 0.45 }
}
}Topic: user:{userID}:orders
Notifies you whenever one of your orders changes state.
Subscribe
{ "action": "subscribe", "topics": ["user:d9f8fb6e:orders"] }Use your own
userIDfrom the authentication response.
Message
{
"type": "data",
"topic": "user:d9f8fb6e:orders",
"ts": 1712000000005,
"data": {
"order_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"market_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "matched",
"order_type": "GTC",
"side": "BUY",
"token_option": "yes",
"limit_price": 0.55,
"amount": 100.0,
"size_matched": 100.0,
"avg_fill_price": 0.55,
"updated_at": "2026-04-04T10:00:05Z"
}
}Status values: pending → signing → submitted → live → matched → won / lost
Topic: user:{userID}:balance
Fires when your balance changes (deposit, bet, settlement, etc.).
Subscribe
{ "action": "subscribe", "topics": ["user:d9f8fb6e:balance"] }Message
{
"type": "data",
"topic": "user:d9f8fb6e:balance",
"ts": 1712000000010,
"data": {
"balance": 245.50,
"updated_at": "2026-04-04T10:00:10Z"
}
}Topic: user:{userID}:notifications
Receives in-app notifications for the user.
Subscribe
{ "action": "subscribe", "topics": ["user:d9f8fb6e:notifications"] }Message
{
"type": "data",
"topic": "user:d9f8fb6e:notifications",
"ts": 1712000000015,
"data": {
"notification_id": "n1b2c3d4",
"title": "Order Matched",
"body": "Your order has been fully matched.",
"created_at": "2026-04-04T10:00:15Z"
}
}Broadcast: event:{eventID}:score
Sent to all connected clients automatically when a live score updates. No subscription required.
Message
{
"type": "data",
"topic": "event:evt-001:score",
"ts": 1712000000020,
"data": {
"event_id": "evt-001",
"home_score": 3,
"away_score": 1,
"period": "2nd Half",
"clock": "67:23"
}
}Broadcast: market:{marketID}:status
Sent to all connected clients automatically when a market's status changes. No subscription required.
Message
{
"type": "data",
"topic": "market:550e8400-e29b-41d4-a716-446655440000:status",
"ts": 1712000000025,
"data": {
"market_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "closed"
}
}Ping / Pong
The server sends a heartbeat every 15 seconds using two mechanisms simultaneously:
- WebSocket protocol ping frame — most WebSocket libraries respond automatically with a pong frame.
- Application-layer ping message — a JSON message
{"type":"ping"}is sent over the text channel, for environments (e.g. some CDN proxies) that do not forward protocol-level ping frames.
If the server does not receive any pong within 30 seconds, the connection is closed.
To respond to the application-layer ping, send:
{ "action": "pong" }Most browser and Node.js WebSocket libraries handle the protocol-level ping automatically, making the application-layer pong optional but recommended for maximum compatibility.
Full Example (JavaScript)
async function connectWinNovaWS(apiKey, apiSecret, userId) {
// 1. Get a short-lived WS token
const { data } = await makeRequest('POST', '/api/ext/v1/ws/token');
// 2. Open connection
const ws = new WebSocket(
`wss://api.winnova.pro/api/ws/ext/v1?token=${data.ws_token}`
);
ws.onopen = () => {
// 3. Subscribe to desired topics (topics is an array)
ws.send(JSON.stringify({
action: 'subscribe',
topics: [
`user:${userId}:orders`,
`user:${userId}:balance`,
'market:550e8400-e29b-41d4-a716-446655440000:price',
]
}));
};
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
switch (msg.type) {
case 'data':
// Differentiate by topic
if (msg.topic === `user:${userId}:orders`) {
console.log('Order status:', msg.data.status);
} else if (msg.topic === `user:${userId}:balance`) {
console.log('New balance:', msg.data.balance);
} else if (msg.topic && msg.topic.endsWith(':price')) {
console.log('Yes price:', msg.data.yes.best_bid, '/', msg.data.yes.best_ask);
} else if (msg.topic && msg.topic.endsWith(':score')) {
console.log('Score:', msg.data.home_score, '-', msg.data.away_score);
}
break;
case 'snapshot':
console.log('Snapshot for topic:', msg.topic, msg.data);
break;
case 'subscribed':
console.log('Subscribed to:', msg.topics);
break;
case 'ping':
// Respond to application-layer ping (protocol-level ping is handled automatically)
ws.send(JSON.stringify({ action: 'pong' }));
break;
case 'auth_expired':
console.warn('Token expired — reconnect with a new token');
ws.close();
break;
}
};
ws.onerror = (err) => console.error('WS error:', err);
ws.onclose = () => console.log('Disconnected — reconnect as needed');
}