Skip to content
PROBORA

WebSockets

Reconnection

Heartbeats, exponential backoff and resumable delivery with last_event_id.

Coming Soon

Coming soon

The WebSocket feed is in development. This reference describes the v1 protocol so you can build against it today; it will be available on the Trader plan and above. Until launch, poll the REST endpoints once per window.

Heartbeats

Sent every 15 seconds. Reconnect if no message arrives for 45 seconds.

heartbeat
{
  "type": "heartbeat",
  "ts": "2026-11-14T20:07:45.000Z"
}

Exponential backoff

When the connection drops, wait before reconnecting and double the delay after each failure: 1 s, 2 s, 4 s … capped at 30 s, with random jitter so that many clients do not reconnect in lockstep. Reset the delay after a connection has been healthy for a minute.

Resuming a stream

After reconnecting and authenticating, send resume with the last event id you processed. The server replays missed events from the last 5 minutes, then continues live.

Client → server
{
  "op": "resume",
  "last_event_id": "evt_01JD3Z4K9C"
}
Server → client
{
  "type": "resumed",
  "replayed": 14,
  "last_event_id": "evt_01JD3ZM2XB"
}

Filling longer gaps

If your last event is older than the replay window, the server answers with {"type":"resume_failed","reason":"window_expired"}. Re-subscribe, then backfill the gap from the REST history and market history endpoints using from.

Reference implementation

import WebSocket from "ws";

const CHANNELS = ["signals:BTC:15m", "markets:BTC:15m"];
let lastEventId = null;
let attempt = 0;

function connect() {
  const ws = new WebSocket("wss://api.probora.xyz/v1/signals", {
    headers: { Authorization: `Bearer ${process.env.PROBORA_API_KEY}` },
  });
  let watchdog;
  const kick = () => {
    clearTimeout(watchdog);
    watchdog = setTimeout(() => ws.terminate(), 45_000); // no message for 45 s
  };

  ws.on("open", () => {
    kick();
    ws.send(JSON.stringify({ op: "subscribe", channels: CHANNELS }));
    if (lastEventId) ws.send(JSON.stringify({ op: "resume", last_event_id: lastEventId }));
    setTimeout(() => (attempt = 0), 60_000);
  });

  ws.on("message", (raw) => {
    kick();
    const event = JSON.parse(raw.toString());
    if (event.id) lastEventId = event.id;
    // handle event…
  });

  ws.on("close", () => {
    clearTimeout(watchdog);
    const delay = Math.min(30_000, 1000 * 2 ** attempt++) * (0.5 + Math.random() / 2);
    setTimeout(connect, delay);
  });
}

connect();

Questions or feedback on the API? [email protected]