Skip to main content
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

  1. Obtain a bearer token via the REST Authorize endpoint (use the same environment as your WS connection).
  2. Connect to wss://api.ekiden.fi/ws/private (or staging equivalent).
  3. 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:
  1. 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).
  2. 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.