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
- 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:
```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:
-
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).
-
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):
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.