Skip to main content

WebSocket

Overview

Websockets enable web applications to maintain fast bidirectional communication with server-side processes, providing real-time data updates, avoiding polling the server through REST API endpoints.

All functionalities with available interfaces for external integration, especially transactional ones, are accessible through REST APIs. Some features with frequent data updates are accessible through Websockets. In your applications, you'll need to use REST API endpoints for some functions and Websockets for others.

Server URL: wss://ws.digitra.com

Request process

A websocket connection goes through this lifecycle:

  • Establish a websocket connection with Digitra.com;
  • (Optional) Authenticate using your API credentials;
  • Send pings at regular intervals;
  • Subscribe to a channel;
  • Receive subscription response;
  • Receive data;
  • Unsubscribe;

All messages exchanged between client and server are written in JSON and follow most of Digitra.com REST API conventions, except if redefined on this documentation.

Connection

A websocket connection can be made using libraries present in most languages. The Digitra.com connection endpoint is defined at the beginning of this document.

Didactic-oriented connection example in Python:

import json
import websocket

# Create a connection with websocket endpoint and define functions to handle websocket events
ws = websocket.WebSocketApp(
url=<websocket_url_endpoint>,
on_message=<function_to_handle_received_messages>,
on_error=<function_to_handle_error_messages>,
on_close=<function_to_handle_closed_connections>
)
ws.run_forever()

# Send messages as subscriptions, authentication, ping etc.
ws.send(
json.dumps(<messages>)
)

Ping

To keep the connection alive, the following message should be sent every 30 seconds:

{"op": "PING"}

To avoid network latency problems, we recommend sending the ping message every 20 seconds. If the server does not receive this message within the defined window time, the socket connection will be closed.

In response, the server will send a pong message:

{"type": "PONG"}

If the pong response is not received within 30 seconds, reconnect.

Subscription

To start receiving data, it is necessary to send a subscription message with the following format:

{"op": "SUBSCRIBE", "channel": "<channel>", "market": "<market>"}
{"op": "SUBSCRIBE", "channel": "ORDERBOOK", "market": "BTC-USDT"}

Here, the 'channel' can be one of those described in the Channels chapter below, and 'market' is an optional field representing one of the available markets (refer to list markets). The obligation of the 'market' field is depending of the channel type.

In response, the server sends a confirmation message, for example:

{"type":"SUBSCRIBED","channel":"ORDERBOOK","market":"BTC-USDT"}

To stop receiving data from a channel, it is necessary to send an unsubscription message:

{"op": "UNSUBSCRIBE", "channel": "<channel>", "market": "<market>"}
{"op": "UNSUBSCRIBE", "channel": "ORDERBOOK", "market": "BTC-USDT"}

Upon receiving this message, the server sends a confirmation message, as shown below:

{"type":"UNSUBSCRIBED", "channel":"ORDERBOOK","market":"BTC-USDT"}

To retrieve all active subscriptions, send a list subscriptions message:

{"op": "SUBSCRIPTIONS"}

In response, the server will list all your active subscriptions, as demonstrated below:

{
"type": "SUBSCRIPTIONS",
"data": [
{
"channel": "ORDERBOOK",
"market": "BTC-USDT"
},
{
"channel": "ORDERS",
"market": "BTC-USDT"}
]
}

Channels

The general response format of channel data is:

  • type: Message type. For channel content, it would be update or error;
  • channel: (optional) If the type were updated, it is the channel subscribed;
  • market: (optional) If the type were updated, it is the market subscribed;
  • error_code: (optional) If the type were error, it is the error code detailed in the following topics;
  • message: (optional) If the type were error, it is a message describing the error.
  • data: (optional) If the type is not error, it is the main content with specific fields according the current channel.

There are some channels for subscription and they correspond to the main content of the equivalent REST endpoints. For the message content details see the documentation of the equivalent REST API endpoint.

  • ORDERBOOK channel provides the orderbook's best 100 orders on either side when a update happens (see /v1/markets/{market_id}/orderbook). The marketfield is required on subscription. After subscription, the server will sent the current orderbook, even if no recent update has happened.
  • ORDERS channel provides the updated order details when any of your orders has an update (see /v1/trade/orders/{order_id}). Requires authentication.
  • FILLS channel provides the trades updates of any of your orders when the order has a trade match (see /v1/trade/fills). Requires authentication.
  • PRICES channel provides the most updated bid and ask offers and other data related with trades on market (see /v1/markets/{market_id}/prices). The marketfield is required on subscription.
  • TRADES channel provides trade data when a trade happens (see /v1/markets/{market_id}/trades). The marketfield is required on subscription.

Authentication

In all authentication-required channels, the connection needs to be authenticated by a signature using your API credentials before subscription.

For channels that do not require authentication, authentication is recommended for support purpose.

To create API credentials, you need to have a verified account, created by the Digitra.com mobile app, and access your profile page on the Digitra.com web application.

An API credential is formed by an API key and a client secret.

The authorization process consists of sending these 3 values upon connection:

  • api_key: "<your_api_key>" used to identify you;
  • timestamp: the current timestamp (see Timestamps Date Type) used to time out old requests;
  • signature: used to ensure that the request was generated by the credential holder. It is generated by SHA256 HMAC of a inline concatenation of timestamp and 'auth' word: {timestamp}auth.

The code below, in Python, and the output can help you to implement the signature:

import time
import hmac
import json
from requests import Request

secret = "<your_api_client_secret>".encode()
timestamp = str(int(time.time() * 1000000))

signature_payload = f"{timestamp}auth".encode()
signature = hmac.new(secret, signature_payload, "sha256").hexdigest()

print(signature_payload.decode())
print(signature)
'1676040464591112auth'
'a7ecc22546b496fb2c6c48e253dec2a1a0a4a497553e023aa4244b1f29b5f9f5'

With generated signature, the authentication is made sending the following message:

{
"op": "AUTH",
"api_key": <your_api_key>,
"timestamp": <timestamp>,
"signature": <signature>
}

As response, the server will send:

{"type: "AUTHENTICATED", "api_key": "<your_api_key>"}

One websocket connection may be logged in to at most one user. If the connection is already authenticated, further attempts to log in will result in error.

Based on what was explained above, using Python with a more didactic-focused code, an authenticated connection can be done like this:

import time
import hmac
import json
import requests
import websocket
import threading

api_key = "a92977146953450a889ad20e7a148c60"

# Define the secret key in bytes.
secret = "ek85VGlhMlFUOF9LQUxqNW16RDFfR2hETk5rWFlPQjlVQVcxUFlZTGpvdF9BVUZBLURMTkNMcllCaU5QSEIwRHFvM3dwVDMxV3IxWmYwLVloVzdaQlE9PQ".encode()

# Define the timestamp in microseconds.
timestamp = str(int(time.time() * 1000000))

# Define the signature payload.
signature_payload = f"{timestamp}auth".encode()

# Sign the payload with the secret key.
signature = hmac.new(secret, signature_payload, "sha256").hexdigest()

# Define a function to print received messages
def on_message(wsapp, message):
print(message)

# Define a function to ping server each 25 seconds
def websocket_send_heartbeat(self, ws):
ws.send(json.dumps({"op": "PING"}))
threading.Timer(25, websocket_send_heartbeat, args=[ws]).start()

# Define a function to login, subscribe and ping after connection
def on_open(ws):
ws.send(json.dumps({"op": "AUTH", "api_key": api_key, "timestamp": timestamp, "signature": signature}))
ws.send(json.dumps({"op": "SUBSCRIBE", "channel": "ORDERS", "market": "BTC-USDT"}))
websocket_send_heartbeat(ws)

# Start a websocket connection
ws = websocket.WebSocketApp(
"wss://ws.digitra.com",
on_message=on_message,
on_open=on_open,
)
ws.run_forever()
{"type": "AUTHENTICATED", "api_key": "a92977146953450a889ad20e7a148c60"}
{"type": "SUBSCRIBED", "channel": "ORDERS", "market": "BTC-USDT"}
{"type": "PONG"}

Limits

100 simultaneous authenticated connections per customer account.

100 simultaneous connections from same IP.

100 new connections from same IP in 5 minutes.

300 sent messages by the client in 5 minute per connection.

IPs that repeatedly exceed the limits may be blocked by the server and API keys may be deleted.

Errors

Any error caused by the client or server will result in a disconnection. As the WebSocket protocol has default status codes (see https://www.rfc-editor.org/rfc/rfc6455#section-7.3), errors will be raised under one of them.

Error list:

WebSocket status codeError codeDescription
1007invalid_jsonInvalid JSON message.
1006no_pingPing message not received by server into the defined time interval.
1009message_too_bigMessage is too big. Max message size is 512 bytes.
1008too_many_connectionsYou reached the maximum connection allowed by one of defined limits.
1008slow_consumptionThere are too many messages waiting consumption.
1008too_many_messagesThere are too many being sent to server.
4000invalid_operationOperation invalid or not set.
4000invalid_channelChannel invalid or not set.
4000invalid_marketInvalid market.
4000missing_required_field::{field}A required field is missing. Check the documentation of the operation requested.
4000channel_subscribedSubscription request for a channel that has already been previously subscribed by the customer.
4000channel_not_subscribedUnsubscription request for a channel that has not been previously subscribed to by the client.
4001authorizedThis connection already have a authentication.
4001unauthorizedThe channel requires authentication not performed yet.
4001invalid_signatureInvalid signature.
4001invalid_timestampThe timestamp is invalid.
4001old_timestampThe timestamp is older than 30 seconds.
1011internal_serverInternal server error.