Websockets

The CryptoIndexSeries Websocket feed provides a single, normalised, low-latency data feed for Crypto Markets. Allowing customers to consume live exchange data, aggregated data, calculated data & news content via single connection.

Connecting to the websocket

Websocket Location: wss://socket.cryptoindexseries.com

Authentication
The WebSocket API expects your API KEY as query parameter with name token. E.g:
wss://socket.cryptoindexseries.com?token={my-api-key-id}

Our servers are implemented using [Socket.IO](https://socket.io/) library (Version 4.14). In order take advantage of its ease of use and features like automatic reconnection - using socket.io on the client side is highly recommended. But you can find details of connecting with bare WebSocket implementation.

Definitions

Some useful definitions:

Symbol: An instrument that includes base, quote currencies with exchange information.

BTC-USDT.BNB -> BTC-USDT pair from Binance
BTC-USDT.CISCALC -> BTC-USDT pair that we (Crypto Indexx Series) calculated from various exchanges
BTC-USDT.CBS -> BTC-USDT pair from CoinBasePro

Domain: The type of data to subscribe to e.g. TRADE, TICKER etc...

Available Domains:

  • TRADE - Raw trade channel
  • TICKER - Calculated Ticker containing 24hour and on-the-day summary data
  • TOB - Top Of Book
  • COB - Consolidated Order Book
  • CANDLE_1M - 1Minute Candles
  • CANDLE_5M - 5Minute Candles
  • CANDLE_15M - 15Minute Candles
  • CANDLE_30M - 30Minute Candles
  • CANDLE_60M - 60Minute Candles
  • CANDLE_1D - 1Day Candles

BTC-USDT.BNB~TICKER -> Ticker data of BTC-USDT from Binance
BTC-USDT.BNB~TRADE -> Trades of BTC-USDT from Binance

Action: subscribe or unsubscribe

When you subscribe to feed of an instrument you start receiving updates for that instrument. When you unsubscribe from an instrument you stop receiving updates from an instrument.

Subscribing to data:

In order to receive updates for an instrument first you need to subscribe to an instrument pair. We are expecting a message similar to this:

{
  "action" : "subscribe", // Our action is subscription
  "data": ["BTC-USDT.BNB~TICKER"] // Multiple instrument can be add here
}

Once the request is received by the server it will send an acknowledgement like this:

OK|SUB|BTC-USDT.BNB~TICKER

A snapshot of the symbol is then sent. Client side applications should cache the snapshot as subsequent updates to a symbol~domain will contain updates only.

{
      "p": {
        // Payload
      },
      "ns": "BNB_BTCUSDT", // Native symbol of the instrument prefixed with exchangeCode_
      "d": "TICKER", // domain
      "seqnum": 6854283, // Sequence number to keep track of updates
      "u_ts": 1625613863270, // Time of broadcast
      "mt": "snapshot", // Message type
      "s": "BTC-USDT.BNB" // CryptoIndexSeries symbol
    }

After receiving the snapshot whenever an update occurs for the symbol~domain it will be published to your feed. For TICKER and COB domains we are only publishing changed fields and it is up to the consuming client to manage the caching & handling of the data for those channels.

If our websocket server experience a gap in data on the backend they will detect it and proactivaly send out a full snapshot (which can be identified via the mt - message type flag in the message.

Consuming clients can check the integrity of data by checkng the sequence numbers (seqnum) in the message. Sequence Numbers always increment by 1 until they reach 999999 and they revert back to 1. A consuming client that detects a 'gap' or missed message they should re-request the snapshot from the server.

An example of this would be as follows:

// First message (Snapshot)
    {
        "p" {
            "lst": 10000
            "v" : 100000000000000
        },
        seqnum: 123
        mt: "snaphot"
    }
    // First update (Valid Seqnum)
    {
        "p" {
            "lst": 10002 // Volume did not change, only latest price changed
        },
        seqnum: 124
        mt: "update"
    }
    // Second update (Invalid Seqnum)
    {
        "p" {
            "lst": 100034 // Volume did not change, only latest price changed
        },
        seqnum: 126 // This means you missed update 125
        mt: "update"
    }
    // In this case you need to send subscribe request again to get full snapshot

Unsubscribe - To unsubscribe from a symbol~domain, send an unsubscribe request like this:

{
    "action" : "unsubscribe", // Our action is un-subscription
    "data": ["BTC-USDT.BNB~TICKER"] // Multiple instrument can be add here
}

You will receive confirmation back like this:

OK|UNSUB|BTC-USDT.BNB~TICKER

Connecting using socket.io client

const options = {
        path: "/",
        transports: ['websocket'],
        query: {
            token: "my-public-api-token"
        }
    };
    // Create socket connection
    const socket = io("wss://socket.cryptoindexseries.com", options);
    /**
     * Handler for handling messages from realtime feed
     * @param message
     */
    function handleMesasge(message) {
        // Do something with message
    }
    /**
     * Subscribe to a instrument
     *
     * @param data E.g. List of symbols to subscribe
     */
    function subscribe(data) {
        // Send subscribe message
        socket.emit('m', {action: 'subscribe', data: data});
        // Bind handler function (m here a channel in socket.io, out of context)
        socket.on("m", handleMesasge);
    }
    /**
     * Unsubscribe from instrument(s)
     *
     * @param List of instrument(s) to unsubscribe
     */
    function unsubscribe(instruments) {
        socket.emit('m', {action: 'unsubscribe', data: instruments});
    }

Connecting using bare websocket client

// WS library used here mimic native browser WebSockets
    const WebSocket = require('ws');
    // Craete connection (with additional parameters)
    const socket = new WebSocket("wss://socket.cryptoindexseries.com?transport=websocket&EIO=4&token=my-public-api-token");
    /**
     * Handler for handling messages from realtime feed
     * @param message
     */
    function handleMesasge(message) {
        // Do something with message
    }
    /**
     * Subscribe to a instrument
     *
     * @param insturments List of symbols to subscribe
     */
    function subscribe(insturments) {
        // First encode message
        const encoded = encodeMessage({action: 'subscribe', data: insturments})
        // Send subscribe message
        socket.send(encoded)
    }
    /**
     * Unsubscribe from instrument(s)
     *
     * @param insturments List of instrument(s) to unsubscribe
     */
    function unsubscribe(instruments) {
        // First encode message
        const encoded = encodeMessage({action: 'subscribe', data: insturments})
        // Send unsubscribe message
        socket.send(encoded);
    }
    /**
     * Encode mesage so socket.io can understand
     * @param data
     */
    function encodeMessage(data) {
        // This should be like 42["m",{...subscribe/unsubscribe action data...}]
        return `42["m",` + JSON.stringify(data) +`]`
    }
    /**
     * Decode message so socket.io can understand
     * @param data
     */
    function decodeMessage(data) {
        // These are reqular messages with that requries simple encoding/decoding
        // This is like 42["m", "{....message data...}"]
        const sliced = message.data.slice(2, message.data.length);
        const json = JSON.parse(sliced);
        const payload = json[1];
        return payload; // Realtime feed message
    }
    // Socket message handler
    socket.onmessage = function handler(message) {
        if (message.data.startsWith(0)) {
            // There is an initialization process due to socket.io protocol this must
            // be completed before subscription events
            socket.send("40");
        } else if (message.data.startsWith(2)) {
            // 2 indicates ping message you need to send pong (3) whenever ping recieved
            // If you don't your connection will closed.
            socket.send("3");
        } else if (message.data.startsWith(42)) {
            // These are reqular messages which require simple encoding/decoding
            const message = decodeMessage(message.data);
            // Use message payload as you wish
            handleMessage(message);
        }
    }