This page explains how to connect to Ekiden WebSocket streams, authenticate for private data, and manage subscriptions. For full schema details, see the AsyncAPI spec.
- Public stream: AsyncAPI → channel
public
- Private stream: AsyncAPI → channel
private
Environments & Endpoints
- Production
- REST:
https://api.ekiden.fi/api/v1
- WS Public:
wss://api.ekiden.fi/ws/public
- WS Private:
wss://api.ekiden.fi/ws/private
- Staging
- REST:
https://api.staging.ekiden.fi/api/v1
- WS Public:
wss://api.staging.ekiden.fi/ws/public
- WS Private:
wss://api.staging.ekiden.fi/ws/private
Choose your environment before connecting. Public feeds require no auth; private feeds require a bearer token from the matching REST environment.
No authentication is required for the public stream. Private stream requires bearer token auth.
Quick Start (JavaScript)
const ws = new WebSocket("wss://api.ekiden.fi/ws/public"); // use staging URL for testing
ws.onopen = () => {
// Subscribe to orderbook and trades for a market
ws.send(JSON.stringify({ op: "subscribe", args: [
"orderbook.1.BTC-USDC",
"trade.BTC-USDC"
], req_id: "100001" }));
};
ws.onmessage = (ev) => {
const msg = JSON.parse(ev.data);
// Handle acks
if (msg.op === "subscribed") console.log("Subscribed:", msg.args);
if (msg.op === "unsubscribed") console.log("Unsubscribed:", msg.args);
if (msg.op === "error") console.warn("Error:", msg.message);
// Ping/pong timestamps are uint64 Unix nanoseconds in the schema; omit `ts` if your language can’t safely represent u64.
if (msg.op === "pong") console.log("Pong:", msg);
// Handle events (market or user, depending on stream)
if (msg.op === "event") {
console.log("Topic:", msg.topic, "Data:", msg.data);
}
};
// Heartbeat every ~20s
setInterval(() => {
// `ts` is optional; when provided it should be Unix nanoseconds (u64).
// Many clients simply omit it and use pong as a liveness check.
ws.send(JSON.stringify({ op: "ping", req_id: "hb" }));
}, 20_000);
Private Stream Authentication
- Obtain a bearer token via the REST Authorize endpoint (use the same environment as your WS connection).
- Connect to
wss://api.ekiden.fi/ws/private (or staging equivalent).
- Send
AuthRequest once connected.
{ "op": "auth", "bearer": "<JWT>", "req_id": "200001" }
Server response examples:
{ "op": "auth", "success": true, "user_id": "0x88a70ff...", "req_id": "200001" }
{ "op": "auth", "success": false, "message": "INVALID_TOKEN", "req_id": "200001" }
After a successful auth you can subscribe to private topics: order, position, execution, account_balance.
Subscribe / Unsubscribe
Request:
{ "op": "subscribe", "args": ["orderbook.1.BTC-USDC"], "req_id": "100002" }
Ack:
{ "op": "subscribed", "args": ["orderbook.1.BTC-USDC"], "req_id": "100002" }
Unsubscribe:
{ "op": "unsubscribe", "args": ["orderbook.1.BTC-USDC"], "req_id": "100003" }
Ack:
{ "op": "unsubscribed", "args": ["orderbook.1.BTC-USDC"], "req_id": "100003" }
Heartbeat (Ping/Pong)
There are two kinds of heartbeats:
-
Application-level ping/pong
- Client sends
op=ping with an optional ts to measure RTT.
- Server replies with
op=pong echoing client_ts and adding server_ts.
- Timestamps containing Unix nanoseconds (
u64).
-
WebSocket-level ping/pong (control frames)
- The server also sends standard WebSocket ping frames at intervals.
- Your WS client library should automatically reply with WS-level pong frames.
- If the server doesn’t receive WS-level pongs within the timeout window, it will close the connection.
Send an application-level ping periodically to measure RTT (optional):
{ "op": "ping", "ts": 1731541800000000000, "req_id": "100004" } // ts in Unix nanoseconds
Pong response:
{ "op": "pong", "server_ts": 1731541800123000000, "client_ts": 1731541800000000000, "req_id": "100004" } // both in Unix nanoseconds
Keep the connection healthy:
- Your client should automatically respond to WebSocket-level pings with pongs.
- Optionally send application-level pings every ~20 seconds to monitor RTT.
- Idle sockets without responding to WS-level pings may be closed by the server.
Rate Limits & Best Practices
- Avoid repeatedly opening/closing sockets. Reuse a connection per environment.
- Batch subscriptions in a single request where possible (multiple topics in
args).
- Backoff and retry on
error responses; watch for policy messages like rate limits.
- Do not assume ordering across different topics; sequence/order applies within a topic only.
For message shapes and fields, refer to AsyncAPI messages: SubscribeRequest, UnsubscribeRequest, Pong, Error, and Event.
Topics
Public stream topics (subscribe on /ws/public):
orderbook.<depth>.<symbol>
trade.<symbol>
ticker.<symbol>
kline.<interval>.<symbol>
all_liquidations.<symbol>
Private stream topics (after auth on /ws/private):
order
position
execution
account_balance
Regex patterns enforced server-side:
^(orderbook\.[^.]+\.[^.]+|trade\.[^.]+|ticker\.[^.]+|kline\.[^.]+\.[^.]+|all_liquidations\.[^.]+)$ # public
^(order|position|execution|account_balance)$ # private
Unified Event Envelope
All streamed payloads share a common envelope:
{
"op": "event",
"topic": "ticker.BTC-USDC",
"server_ts_ms": 1731541800000,
"data": { /* object or array variant */ }
}
Orderbook events additionally include a type field (snapshot or delta).
data variants:
OrderBookSnapshot (orderbook events also include type: "snapshot" | "delta")
PublicTrade[]
TickerSnapshot
KlineSnapshot
Order[], Execution[], Position[], AccountBalance (private)
Note: server_ts_ms in the event envelope is always milliseconds.