Access URL

Sandbox Environment

  • Public stream: wss://stream.sim.obibiz.com/quote/ws/v1
  • Private stream: wss://stream.sim.obibiz.com/api/v1/ws/{listenKey}

Production Environment

  • Public stream: wss://stream-pro.hashkey.com/quote/ws/v1
  • Private stream: wss://stream-pro.hashkey.com/api/v1/ws/{listenKey}

Heartbeat check

When a user's websocket client application successfully connects to HashKey websocket server, the client is recommended to initiate a periodic heartbeat message (ping message) containing the sessionID every 10 seconds, which is used to keep alive the connection

Ping message format is as follows:

// From Sent by the user
{
    "ping":1691473241907
}

When the user receives the above Ping message from the websocket Client application, it will return a Pong message containing the current timestamp.

The Pong message format is as follows:

// From Websocket Server
{
    "pong":1691473283965
}

Automatic disconnection mechanism (Only for Private Stream)

The websocket sends a ping every 30 seconds, if the websocket server sends two Ping messages in a row, but neither Pong is returned, the websocket link will be automatically disconnected

// From Websocket Server
{
    "ping":1691473241907
}

Websocket sample

import hashlib
import hmac
import json
import time
import websocket
import logging
import threading

########################################################################################################################
# Test Websocket API 

# Copyright: Hashkey Trading 2023
########################################################################################################################


class WebSocketClient:
    def __init__(self):
        self._logger = logging.getLogger(__name__)
        self._ws = None
        self._ping_thread = None

    def _on_message(self, ws, message):
        self._logger.info(f"Received message: {message}")
        data = json.loads(message)
        if "pong" in data:
            # Received a pong message from the server
            self._logger.info("Received pong message")
        # Handle the received market data here

    def _on_error(self, ws, error):
        self._logger.error(f"WebSocket error: {error}")

    def _on_close(self, ws):
        self._logger.info("Connection closed")

    def _on_open(self, ws):
        self._logger.info("Subscribe topic")
        sub = {
            "symbol": "BTCUSD",
            "topic": "trade",
            "event": "sub",
            "params": {
                "binary": False
            },
            "id": 1
        }
        ws.send(json.dumps(sub))
        
        # Start the ping thread after connecting
        self._start_ping_thread()
    

    def _start_ping_thread(self):
        def send_ping():
            while self._ws:
                ping_message = {
                    "ping": int(time.time() * 1000)  # Send a timestamp as the ping message
                }
                self._ws.send(json.dumps(ping_message))
                self._logger.info(f"Send ping message: {ping_message}")
                time.sleep(5)
        
        self._ping_thread = threading.Thread(target=send_ping)
        self._ping_thread.daemon = True
        self._ping_thread.start()

    def unsubscribe(self):
        if self._ws:
            self._logger.info("Unsubscribe topic")
            unsub = {
                "symbol": "BTCUSD",
                "topic": "trade",
                "event": "cancel",
                "params": {
                    "binary": False
                },
                "id": 1
            }
            self._ws.send(json.dumps(unsub))

    def connect(self):
        base_url = 'wss://stream-pro.sim.hashkeydev.com'
        endpoint = 'quote/ws/v1'
        stream_url = f"{base_url}/{endpoint}"
        self._logger.info(stream_url)
        self._logger.info(f"Connecting to {stream_url}")

        self._ws = websocket.WebSocketApp(stream_url,
                                          on_message=self._on_message,
                                          on_error=self._on_error,
                                          on_close=self._on_close)
        self._ws.on_open = self._on_open

        self._ws.run_forever()

if __name__ == '__main__':
    logging.basicConfig(level=logging.INFO)

    client = WebSocketClient()
    client.connect()

import hashlib
import hmac
import json
import time
import websocket
import logging
import threading
import requests
import datetime

########################################################################################################################
# Test Websocket API 

# Copyright: Hashkey Trading 2023
########################################################################################################################


class WebSocketClient:
    def __init__(self, user_key, user_secret, subed_topic=[]):
        self.user_key = user_key
        self.user_secret = user_secret
        self.subed_topic = subed_topic
        self.listen_key = None
        self._logger = logging.getLogger(__name__)
        self._ws = None
        self._ping_thread = None
        self.last_listen_key_extend = time.time()

    def generate_listen_key(self):
        params = {
            'timestamp': int(time.time() * 1000),
        }
        api_headers = {
            'X-HK-APIKEY': self.user_key,
            'content-type': 'application/x-www-form-urlencoded;charset=UTF-8',
        }
        signature = self.create_hmac256_signature(secret_key=self.user_secret, params=params)
        params.update({
            'signature': signature,
        })
        response = requests.post(url=f"https://api-pro.sim.hashkeydev.com/api/v1/userDataStream", headers=api_headers, data=params)
        data = response.json()
        if 'listenKey' in data:
            self.listen_key = data['listenKey']
            self._logger.info(f"Generated listen key: {self.listen_key}")
        else:
            raise Exception("Failed to generate listen key")

    def extend_listenKey_timeLimit(self):
        params = {
            'timestamp': int(time.time() * 1000),
            'listenKey': self.listen_key,
        }
        api_headers = {
            'X-HK-APIKEY': self.user_key,
            'content-type': 'application/x-www-form-urlencoded;charset=UTF-8',
        }
        signature = self.create_hmac256_signature(secret_key=self.user_secret, params=params)
        params.update({
            'signature': signature,
        })
        response = requests.put(url=f"https://api-pro.sim.hashkeydev.com/api/v1/userDataStream", headers=api_headers, data=params)
        if response.status_code == 200:
            self._logger.info("Successfully extended listen key validity.")
        else:
            self._logger.error("Failed to extend listen key validity.")

    def create_hmac256_signature(self, secret_key, params, data=""):
        for k, v in params.items():
            data = data + str(k) + "=" + str(v) + "&"
        signature = hmac.new(secret_key.encode(), data[:-1].encode(), digestmod=hashlib.sha256).hexdigest()
        return signature

    def _on_message(self, ws, message):
        current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")
        self._logger.info(f"{current_time} - Received message: {message}")
        
        data = json.loads(message)
        if "pong" in data:
            self._logger.info("Received pong message")
        # Handle other messages here

    def _on_error(self, ws, error):
        self._logger.error(f"WebSocket error: {error}")

    def _on_close(self, ws):
        self._logger.info("Connection closed")

    def _on_open(self, ws):
        self._logger.info("Subscribing to topics")
        for topic in self.subed_topic:
            sub = {
                "symbol": "BTCUSD",
                "topic": topic,
                "event": "sub",
                "params": {
                    "limit": "100",
                    "binary": False
                },
                "id": 1
            }
            ws.send(json.dumps(sub))
        self._start_ping_thread()

    def _start_ping_thread(self):
        def send_ping():
            while self._ws:
                current_time = time.time()
                if current_time - self.last_listen_key_extend > 1800:  # Extend listen key every 30 minutes
                    self.extend_listenKey_timeLimit()
                    self.last_listen_key_extend = current_time

                ping_message = {"ping": int(time.time() * 1000)}
                self._ws.send(json.dumps(ping_message))
                self._logger.info(f"Sent ping message: {ping_message}")
                time.sleep(5)

        self._ping_thread = threading.Thread(target=send_ping)
        self._ping_thread.daemon = True
        self._ping_thread.start()

    def unsubscribe(self):
        if self._ws:
            self._logger.info("Unsubscribing from topics")
            for topic in self.subed_topic:
                unsub = {
                    "symbol": "BTCUSD",
                    "topic": topic,
                    "event": "cancel_all",
                    "params": {
                        "limit": "100",
                        "binary": False
                    },
                    "id": 1
                }
                self._ws.send(json.dumps(unsub))

    def connect(self):
        if not self.listen_key:
            self.generate_listen_key()

        base_url = 'wss://stream-pro.sim.hashkeydev.com'
        endpoint = f'api/v1/ws/{self.listen_key}'
        stream_url = f"{base_url}/{endpoint}"
        self._logger.info(f"Connecting to {stream_url}")

        self._ws = websocket.WebSocketApp(stream_url,
                                          on_message=self._on_message,
                                          on_error=self._on_error,
                                          on_close=self._on_close)
        self._ws.on_open = self._on_open

        self._ws.run_forever()

if __name__ == '__main__':
    logging.basicConfig(level=logging.INFO)
    user_key = "YOUR_USER_KEY"
    user_secret = "YOUR_USER_SECRET"
    subed_topics = ["trade"]
    client = WebSocketClient(user_key, user_secret, subed_topics)
    client.connect()

Public Stream


Kline_$interval

Update frequency: 300ms

Subscription request parameters

PARAMETERTYPEReq'dExample valuesDESCRIPTION
symbolSTRINGYBTCUSDTName of currency pair
topicSTRINGYkline_1mTopic name, default: "kline"
eventSTRINGYsubEvent type
paramsSTRINGY"binary": falseParameter

Request Example:

sub = {
        "symbol": "BTCUSDT",
         "topic": "kline_1m",
         "event": "sub",
         "params": {
             "binary": False
         },
         "id": 1
}

Response content

PARAMETERTYPEExample valuesDESCRIPTION
symbolSTRINGBTCUSDTCurrency pair ID
symbolNameSTRINGBTCUSDTCurrency pair name
topicSTRINGklineTopic name
paramsJSON ObjectRequest expanded parameters
params.realtimeIntervalSTRING24hTime period
params.klineTypeSTRING1mKline Type
params.binaryBOOLEANfalseWhether it is a binary type
dataJSON ArrayReturn data
data.tINT641688199660000Timestamp in Milliseconds
data.sSTRINGBTCUSDTCurrency pair ID
data.snSTRINGBTCUSDTCurrency pair name
data.cSTRING10002Close price
data.hSTRING10002High price
data.lSTRING10002Low price
data.oSTRING10002Open price
data.vSTRING0Base Asset Volume
fBOOLEANtrueWhether it is the first return value
sendTimeINT641688199705619Timetamp in Milliseconds
idSTRING1Message ID

Response Example:

{
  "symbol": "BTCUSDT",
  "symbolName": "BTCUSDT",
  "topic": "kline",
  "params": {
    "realtimeInterval": "24h",
    "klineType": "1m",
    "binary": "false"
  },
  "data": [
    {
      "t": 1688199660000,          // Event Time
      "s": "BTCUSDT",              // Symbol
      "sn": "BTCUSDT",             // Symbol Name
      "c": "10002",                // Close Price
      "h": "10002",                // High Price
      "l": "10002",                // Low Price
      "o": "10002",                // Open Price
      "v": "0"                     // Base Asset Volume
    }
  ],
  "f": true,                       // Is it the first return
  "sendTime": 1688199705619,
  "shared": false,
  "id": "1"
}

Realtimes

Update frequency: 500ms

Subscription request parameters

PARAMETERTYPEReq'dExample valuesDESCRIPTION
symbolSTRINGYBTCUSDTCurrency pair
topicSTRINGYrealtimesTopic name, default: "realtimes"
eventSTRINGYsubEvent Type
paramsArrayY"binary": false
""
Parameter

Request Example:

sub = {
        "symbol": "BTCUSDT",
         "topic": "realtimes",
         "event": "sub",
         "params": {
             "binary": False
         },
         "id": 1
}

Response content

PARAMETERTYPEExample valuesDESCRIPTION
symbolSTRINGBTCUSDTCurrency pair ID
symbolNameSTRINGBTCUSDTCurrency pair name
topicSTRINGrealtimesTopic name
paramsJSON ObjectRequest expanded parameter
params.realtimeIntervalSTRING24hTime period
params.binaryBOOLEANfalseWhether it is a binary type
params.dumpScaleINT6410Number of layers in either of order book sides
dataJSON ArrayReturn data
data.tINT641688199300011Timestamp in Milliseconds
data.sSTRINGBTCUSDTCurrency pair ID
data.snSTRINGBTCUSDTCurrency pair name
data.cSTRING10002Close price
data.hSTRING10002High price
data.lSTRING10002Low price
data.oSTRING10002Open price
data.vSTRING0Volume (in base currency)
data.qvSTRING0Volume(in quote currency)
data.mSTRING024H range
data.eINT64301Exchange ID
fBOOLEANtrueWhether it is the first return value
sendTimeINT64falseWhether to share
idSTRING1Message ID

Response Example:

{
  "symbol": "BTCUSDT",
  "symbolName": "BTCUSDT",
  "topic": "realtimes",
  "params": {
    "realtimeInterval": "24h",
    "binary": "false"
  },
  "data": [
    {
      "t": 1688199300011,      // Event Time
      "s": "BTCUSDT",          // Symbol
      "sn": "BTCUSDT",         // Symbol Name
      "c": "10002",            // Close Price
      "h": "10002",            // High Price
      "l": "10002",            // Low Price
      "o": "10002",            // Open Price
      "v": "0",                // Total traded base asset volume
      "qv": "0",               // Total traded quote asset volume
      "m": "0",                // 24H Range
      "e": 301                 // Trade Id
    }
  ],
  "f": true,
  "sendTime": 1688199337756,
  "shared": false,
  "id": "1"
}

Trade

Upon successful subscription to our WebSocket API, you will receive an update of the most recent 60 trades for the symbol pair subscribed.

Update frequency: 300ms

Subscription request parameters

PARAMETERTYPEReq'dExample valuesDESCRIPTION
symbolSTRINGYBTCUSDTCurrency pair
topicSTRINGYtradeTopic name, default: "trade"
eventSTRINGYsubEvent type
paramsSTRINGY"binary": FalseParameter

Request Example:

sub = {
        "symbol": "BTCUSDT",
         "topic": "trade",
         "event": "sub",
         "params": {
             "binary": False
         },
         "id": 1
}

Response content

PARAMETERTYPEExample valuesDESCRIPTION
symbolSTRINGBTCUSDTCurrency pair ID
symbolSTRINGBTCUSDTCurrency pair name
topicSTRINGtradeTopic name
paramsJSON ObjectRequest expanded parameters
params.realtimeIntervalSTRING24hTime period
params.binaryBOOLEANfalseWhether it is a binary type
dataJSON ArrayReturn data
data.vSTRING1447335405363150849Transaction record ID
data.tSTRING1687271825415Timestamp corresponding to transaction tine in milliseconds
data.pSTRING10001Traded price
data.qSTRING0.001Traded quantity
data.mSTRINGfalseBuying and selling direction

true: buy
false: sell
fBOOLEANtrueWhether it is the first return value
sendTimeINT641688198964293Timestamp in milliseconds
sharedBOOLEANfalseWhether to share
idSTRING1Message ID

Response Example:

{
  "symbol": "BTCUSDT",
  "symbolName": "BTCUSDT",
  "topic": "trade",
  "params": {
    "realtimeInterval": "24h",
    "binary": "false"
  },
  "data": [
    {
      "v": "1447335405363150849",   // Transaction ID
      "t": 1687271825415,           // Time
      "p": "10001",                 // Price
      "q": "0.001",                 // Quantity
      "m": false                    // true = buy, false = sell
    },
    {
      "v": "1447337171483901952",   
      "t": 1687272035953,
      "p": "10001.1",
      "q": "0.001",
      "m": true
    },
    {
      "v": "1447337410752167937",
      "t": 1687272064476,
      "p": "10003",
      "q": "0.001",
      "m": false
    },
    {
      "v": "1452060966291521544",
      "t": 1687835156176,
      "p": "10002",
      "q": "0.001",
      "m": false
    }
  ],
  "f": true,
  "sendTime": 1688198964293,
  "shared": false,
  "id": "1"
}

Depth

Request the depth of the orderbook, can request up to limit of 100

Update frequency: 300ms

Request Example:

Subscription request parameters

PARAMETERTYPEReq'dExample valuesDESCRIPTION
symbolSTRINGYBTCUSDTCurrency pair
topicSTRINGYdepthTopic name, default: "depth"
eventSTRINGYsubEvent type
paramsSTRINGY"binary": FalseParameter
sub = {
        "symbol": "BTCUSDT",
         "topic": "depth",
         "event": "sub",
         "params": {
             "binary": False
         },
         "id": 1
}

Response content

PARAMETERTYPEExample valuesDESCRIPTION
symbolSTRINGBTCUSDTCurrency pair ID
symbolSTRINGBTCUSDTCurrency pair name
topicSTRINGdepthTopic name
paramsJSON ObjectRequest expanded parameters
params.realtimeIntervalSTRING24hTime period
params.binaryBOOLEANfalseWhether it is a binary type
dataJSON ArrayReturn data
data.eINT64301Latest transaction record ID
data.sSTRINGBTUSDTCurrency pair
data.tINT641688199202314Timestamp in milliseconds
data.vSTRING6881_18Base asset volume
data.aJSON Array[
"10004",
"0.001"
]
Ask price and quantity
data.bJSON Array[
"10004",
"0.001"
]
Bid price and quantity
fBOOLEANtrueWhether it is the first return value
sendTimeINT641688199482822Timestamp in milliseconds
sharedBOOLEANfalseWhether to share
idSTRING1Message id

Response Example:

{
  "symbol": "BTCUSDT",
  "symbolName": "BTCUSDT",
  "topic": "depth",
  "params": {
    "realtimeInterval": "24h",
    "binary": "false"
  },
  "data": [
    {
      "e": 301,
      "s": "BTCUSDT",               // Symbol
      "t": 1688199202314,           // Time
      "v": "6881_18",               // Base Asset Volume
      "b": [],                      // Bids
      "a": [
        [
          "10004",                  // Price
          "0.001"                   // Quantity
        ],
        [
          "18000",
          "0.05"
        ],
        [
          "27000",
          "0.01"
        ],
        [
          "28000",
          "0.09"
        ]
      ],
      "o": 0
    }
  ],
  "f": true,                       // Is it the first return
  "sendTime": 1688199482822,
  "shared": false,
  "id": "1"
}

Private Stream

  • A User Data Stream listenKey is valid for 60 minutes after creation.
  • Doing a PUT on a listenKey will extend its validity for 60 minutes.
  • Doing a DELETE on a listenKey will close the stream and invalidate the listenKey .
  • Doing a POST on an account with an active listenKey will return the currently active listenKeyand extend its validity for 60 minutes.
  • User feeds are accessible via /api/v1/ws/ (e.g. wss://#HOST/api/v1/ws/)
    User feed payloads are not guaranteed to be up during busy times; make sure to order updates with E

For example in Postman, you may test our websocket with listenkey

  1. Create a new request and select Websocket
  1. Input wss://stream.sim.obibiz.com/api/v1/ws/{listenkey} and click "Connect"

Payload: Account Update

Update frequency: Realtime

Subscription request parameters
No

Subscription Request Example
No

Subscription return parameter

Whenever the account balance changes, an event outboundAccountInfo is sent containing the assets that may have been moved by the event that generated the balance change.

[
  {
    "e": "outboundAccountInfo",   // Event type 
																	// outboundAccountInfo // Trading Account 
    															// outboundCustodyAccountInfo // Custody Account (To be released soon)
    															// outboundFiatAccountInfo // Fiat Account (To be released soon)
    "E": 1499405658849,           // Event time
    "T": true,                    // Can trade
    "W": true,                    // Can withdraw
    "D": true,                    // Can deposit
    "B": [                        // Balances changed 
      {
        "a": "LTC",               // Asset 
        "f": "17366.18538083",    // Free amount 
        "l": "0.00000000"         // Locked amount 
      }
    ]
  }
]

Payload: Order Update

Update frequency: Realtime

Subscription request parameters
No

Subscription Request Example
No

Subscription return parameter

Order updates are updated through the executionReport event. Check out the API docs and the relevant enum definitions below. The average price can be found by dividing Z by z.

Execution Type

  • NEW
  • PARTIALLY_FILLED
  • FILLED
  • PARTIALLY_CANCELED
  • CANCELED
  • REJECTED
[
  {
    "e": "executionReport",        // Event type 
    "E": 1499405658658,            // Event time 
    "s": "ETHBTC",                 // Symbol 
    "c": 1000087761,               // Client order ID 
    "S": "BUY",                    // Side 
    "o": "LIMIT",                  // Order type 
    "f": "GTC",                    // Time in force 
    "q": "1.00000000",             // Order quantity 
    "p": "0.10264410",             // Order price 
"reqAmt": "1000",	                 // Requested cash amount (To be released)
    "X": "NEW",                    // Current order status
    "d": "1234567890123456789"     // Execution ID
    "i": 4293153,                  // Order ID 
    "l": "0.00000000",             // Last executed quantity 
    "r": "0"                       // unfilled quantity
    "z": "0.00000000",             // Cumulative filled quantity 
    "L": "0.00000000",             // Last executed price
    "V": "26105.5"                 // average executed price
    "n": "0",                      // Commission amount 
    "N": null,                     // Commission asset 
    "u": true,                     // Is the trade normal, ignore for now 
    "w": true,                     // Is the order working? Stops will have
    "m": false,                    // Is this trade the maker side?
    "O": 1499405658657,            // Order creation time 
    "Z": "0.00000000",             // Cumulative quote asset transacted quantity 
    "x": "USER_CANCEL"             // Order cancel reject reason
  }
]

Payload: Ticket push

Update frequency: Realtime

Subscription request parameters
No

Subscription Request Example
No

Subscription return parameter

[
    {
        "e": "ticketInfo",                // Event type 
        "E": "1668693440976",             // Event time 
        "s": "BTCUSDT",                   // Symbol 
        "q": "0.205",                     // quantity 
        "t": "1668693440899",             // time 
        "p": "441.0",                     // price 
        "T": "1291488620385157122",       // ticketId
        "o": "1291488620167835136",       // orderId 
        "c": "1668693440093",             // clientOrderId 
        "O": "1291354087841869312",       // matchOrderId 
        "a": "1286424214388204801",       // accountId 
        "A": 0,                           // ignore 
        "m": false,                       // isMaker 
        "S": "SELL"                       // side  SELL or BUY
    }
]