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 beupdate
orerror
;channel
: (optional) If the type wereupdated
, it is the channel subscribed;market
: (optional) If the type wereupdated
, it is the market subscribed;error_code
: (optional) If the type wereerror
, it is the error code detailed in the following topics;message
: (optional) If the type wereerror
, it is a message describing the error.data
: (optional) If the type is noterror
, 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). Themarket
field 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). Themarket
field is required on subscription.TRADES
channel provides trade data when a trade happens (see /v1/markets/{market_id}/trades). Themarket
field 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 oftimestamp
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 code | Error code | Description |
---|---|---|
1007 | invalid_json | Invalid JSON message. |
1006 | no_ping | Ping message not received by server into the defined time interval. |
1009 | message_too_big | Message is too big. Max message size is 512 bytes. |
1008 | too_many_connections | You reached the maximum connection allowed by one of defined limits. |
1008 | slow_consumption | There are too many messages waiting consumption. |
1008 | too_many_messages | There are too many being sent to server. |
4000 | invalid_operation | Operation invalid or not set. |
4000 | invalid_channel | Channel invalid or not set. |
4000 | invalid_market | Invalid market. |
4000 | missing_required_field::{field} | A required field is missing. Check the documentation of the operation requested. |
4000 | channel_subscribed | Subscription request for a channel that has already been previously subscribed by the customer. |
4000 | channel_not_subscribed | Unsubscription request for a channel that has not been previously subscribed to by the client. |
4001 | authorized | This connection already have a authentication. |
4001 | unauthorized | The channel requires authentication not performed yet. |
4001 | invalid_signature | Invalid signature. |
4001 | invalid_timestamp | The timestamp is invalid. |
4001 | old_timestamp | The timestamp is older than 30 seconds. |
1011 | internal_server | Internal server error. |