inNovaAPI
WebSocket

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.

TopicDescriptionRequired Scope
market:{tokenID}:bookOrderbook depth updates for a tokenws:read
market:{marketID}:pricePrice updates for a marketws:read
user:{userID}:ordersYour order status changesws:read
user:{userID}:balanceYour balance changesws:read
user:{userID}:notificationsYour notificationsws:read

Broadcast Topics (no subscription needed)

These are pushed to all connected clients automatically — no subscribe message required. Messages also arrive with "type": "data".

TopicDescription
event:{eventID}:scoreLive score updates
market:{marketID}:statusMarket 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": { ... }
}
FieldDescription
typeMessage type: snapshot, data, subscribed, unsubscribed, ping, error, auth_expired
topicThe topic this message belongs to
tsServer timestamp (Unix milliseconds)
dataPayload (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 userID from 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: pendingsigningsubmittedlivematchedwon / 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:

  1. WebSocket protocol ping frame — most WebSocket libraries respond automatically with a pong frame.
  2. 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');
}