# Real-time stream example

> A complete reconnecting WebSocket client for the Crius stream in Node.js - subscribe to new launches, trades and prices on Robinhood Chain, resume after disconnects and handle reorgs.

Source: https://crius.sh/docs/examples/stream/

This client subscribes to every new launch and graduation plus the trades of one token, reconnects with backoff, resumes the messages it missed, and undoes reorged events. It runs on Node.js 22+ (built-in `WebSocket`) with a key that has the `stream` scope.

```js
// stream.mjs: node stream.mjs
const KEY = process.env.CRIUS_API_KEY;
const TOKEN = "0x7b8892cea949bc60d494681d1bc3ea96071ab0b8";
const CHANNELS = ["launches:all", "graduations:all", `trades:${TOKEN}`, `price:${TOKEN}`];

const lastSeq = {}; // channel -> last seq seen, for resuming
let backoff = 1000;

function connect() {
  const ws = new WebSocket(`wss://api.crius.sh/v1/robinhood/stream?api_key=${KEY}`);
  let idle;
  const touch = () => {
    clearTimeout(idle);
    idle = setTimeout(() => ws.close(), 45_000); // heartbeats come every 15 s
  };

  ws.onopen = () => {
    backoff = 1000;
    touch();
    ws.send(JSON.stringify({ op: "subscribe", channels: CHANNELS }));
    if (Object.keys(lastSeq).length) ws.send(JSON.stringify({ op: "resume", seqs: lastSeq }));
  };

  ws.onmessage = (e) => {
    touch();
    const m = JSON.parse(e.data);
    if (m.seq) lastSeq[m.ch] = m.seq;
    switch (true) {
      case m.ch === "reorg":
        console.warn(`reorg: blocks ${m.data.removed_blocks.join(", ")} replaced`);
        break;
      case m.data?.removed:
        console.warn(`undo ${m.ch} ${m.data.id}`); // reverse whatever you did for this event id
        break;
      case m.ch.startsWith("launches:"):
        console.log(`new ${m.data.launchpad} token ${m.data.address} by ${m.data.creator}`);
        break;
      case m.ch.startsWith("graduations:"):
        console.log(`graduated ${m.data.token} → pool ${m.data.pool}`);
        break;
      case m.ch.startsWith("trades:"):
        console.log(`${m.data.type} $${m.data.value_usd} by ${m.data.trader}`);
        break;
      case m.ch.startsWith("price:"):
        console.log(`price $${m.data.usd}`);
        break;
      case m.ch === "error":
        console.error(m.data);
        break;
    }
  };

  ws.onclose = () => {
    clearTimeout(idle);
    setTimeout(connect, backoff + Math.random() * 500);
    backoff = Math.min(backoff * 2, 30_000);
  };
  ws.onerror = () => ws.close();
}

connect();
```

> **Note:**
> The replay buffer holds 60 seconds of messages. After a longer outage, re-sync from REST (for example
> [`/trades`](https://crius.sh/docs/reference/trades/#trade-tape) since your last `ts`) before relying on the stream again.

## Browser version

Browsers can't set headers on WebSockets, so the key would have to go in the URL (`?api_key=`), where anyone can read it from your page. Connect from your backend instead and forward the events to your page (for example over Server-Sent Events). Keys restricted to browser origins are read-only and can't open the stream.
