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/0x88a70ff...",
        "trade/0x88a70ff..."
    ], 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);
    if (msg.op === "pong") console.log("RTT ns:", msg.server_ts - (msg.client_ts ?? 0)); // ping/pong timestamps are in nanoseconds

    // Handle events (market or user, depending on stream)
    if (msg.op === "event") {
        console.log("Topic:", msg.topic, "Data:", msg.data);
    }
};

// Heartbeat every ~20s
setInterval(() => {
    // Use Unix nanoseconds for ping `ts`
    ws.send(JSON.stringify({ op: "ping", ts: BigInt(Date.now()) * 1_000_000n, 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:

```json
{ "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, fill.

Subscribe / Unsubscribe

Request:
{ "op": "subscribe", "args": ["orderbook/0x88a70ff..."], "req_id": "100002" }
Ack:
{ "op": "subscribed", "args": ["orderbook/0x88a70ff..."], "req_id": "100002" }
Unsubscribe:
{ "op": "unsubscribe", "args": ["orderbook/0x88a70ff..."], "req_id": "100003" }
Ack:
{ "op": "unsubscribed", "args": ["orderbook/0x88a70ff..."], "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 in Unix nanoseconds to measure RTT and clock skew.
    • Server replies with op=pong echoing client_ts and adding server_ts (both in ns).
  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": 1681234567890123114, "req_id": "100004" } // ts in Unix nanoseconds
Pong response:
{ "op": "pong", "server_ts": 1681234567890123456, "client_ts": 1681234567890123114, "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/{market_addr}
  • trade/{market_addr}
  • ticker/{market_addr}
Private stream topics (after auth on /ws/private):
  • order
  • fill
  • position
Regex patterns enforced server-side:
^(orderbook|trade|ticker)/[^/\s]+$  # public
^(order|fill|position)$             # private

Unified Event Envelope

All streamed payloads share a common envelope:
{
    "op": "event",
    "topic": "ticker/0x...",
    "data": { /* object or array variant */ }
}
data variants:
  • OrderbookSnapshot | OrderbookDelta
  • AggregatedTrades (with trades array of Trade objects)
  • TickerSnapshot
  • OrderResponse[], FillResponse[], PositionResponse[] (private)
Deprecated timestamps: many payloads include both timestamp (seconds, deprecated) and timestamp_ms (milliseconds). Prefer timestamp_ms.