WebSocket APIHow-tos

Stay connected

Answer the server's heartbeats, handle its reconnect notices, and reconnect with backoff after a disconnect.

A WebSocket connection can close for many reasons: a missed heartbeat, a server shutting down, a network change. A client that stays connected does three things: it answers heartbeats, it moves to a new connection when the server asks, and it reconnects with backoff after any other disconnect.

Answer heartbeats

Every 15 seconds the server sends a heartbeat with the current time. The first one arrives about 15 seconds after you connect:

{ "type": "M", "topic": "heartbeat", "data": 1736696400000 }

Echo its data back before the next one:

{ "method": "heartbeat", "data": 1736696400000 }

The echo has no requestId and gets no response. A missing, late or different value closes the connection with code 1000 at the next heartbeat.

Handle reconnect notices

Before a server shuts down, for example during a release, it sends a notice on the system topic:

{
  "type": "M",
  "topic": "system",
  "data": { "event": "reconnect", "reason": "server_shutdown", "graceMs": 7431 }
}

The connection keeps delivering for about graceMs, then closes with code 1001 and the reason server draining. Use that time to move to a new connection. The examples below reconnect right away. If you can't miss a single update, open the new connection and subscribe on it before you close the old one.

Reconnect with backoff

After any other disconnect, reconnect right away. If that fails, wait longer after each failed attempt, for example 1, 2, 4 and 8 seconds, then 10 seconds between tries, so you don't flood a server that is down. Add up to half a second of random jitter, so that many clients don't reconnect at the same moment. Reset the wait once a connection opens, so the next disconnect reconnects right away again.

A new connection starts with no subscriptions, and messages sent while you were away aren't replayed. So on every connection:

  1. Subscribe to every topic you need.
  2. Refetch from the REST API anything you track, such as your open orders or the market's status.

The whole example

This keeps a subscription running through heartbeats, reconnect notices and disconnects:

// Keep subscriptions running: echo heartbeats, and reconnect after any
// disconnect, subscribing again each time.
// Runs in browsers and in Node.js 22 or later, which have WebSocket built in.
const url = "wss://ws.predict.fun/ws?apiKey=YOUR_API_KEY";
// MARKET_ID: a market ID, like the ones the Market data example prints.
const topics = ["predictOrderbook/MARKET_ID"];

let attempt = 0;

function connect() {
  const ws = new WebSocket(url);
  let reconnectNow = false;

  ws.addEventListener("open", () => {
    attempt = 0;
    // A new connection has no subscriptions, so subscribe to every topic.
    topics.forEach((topic, i) => {
      ws.send(
        JSON.stringify({
          method: "subscribe",
          requestId: i + 1,
          params: [topic],
        }),
      );
    });
    // Updates sent while you were disconnected are not replayed. Refetch
    // anything you track from the REST API here.
  });

  ws.addEventListener("message", (event) => {
    const message = JSON.parse(event.data);
    if (message.topic === "heartbeat") {
      // Echo each heartbeat, or the server closes the connection.
      ws.send(JSON.stringify({ method: "heartbeat", data: message.data }));
    } else if (
      message.topic === "system" &&
      message.data.event === "reconnect"
    ) {
      // The server is shutting down, so move to a new connection right away.
      reconnectNow = true;
      ws.close();
    } else if (message.type === "R" && !message.success) {
      console.error(
        `Request ${message.requestId} failed: ${message.error.message}`,
      );
    } else if (message.type === "M") {
      console.log(`Update on ${message.topic}`);
    }
  });

  ws.addEventListener("close", (event) => {
    let delay = 0;
    if (!reconnectNow) {
      // Reconnect at once the first time. While reconnecting keeps failing,
      // wait 1, 2, 4 and 8 seconds, then 10 seconds between tries. Up to half a
      // second of jitter keeps clients from all reconnecting at the same moment.
      const wait =
        attempt === 0 ? 0 : Math.min(1000 * 2 ** (attempt - 1), 10_000);
      delay = wait + Math.random() * 500;
      attempt++;
    }
    console.log(
      `Closed (${event.code}). Reconnecting in ${Math.round(delay)} ms`,
    );
    setTimeout(connect, delay);
  });
}

connect();

For a complete client with typed messages and subscription tracking, see the client example.

On this page