Back to home
Crypto API

How to Stream Live Crypto Prices in JavaScript (SSE and WebSockets)

Coinranking
Coinranking
How to Stream Live Crypto Prices in JavaScript (SSE and WebSockets)

This guide streams live crypto prices in JavaScript with the Coinranking API, over Server-Sent Events and over WebSockets. If you have not chosen a transport yet, start with SSE vs WebSockets for real-time crypto prices.

You get working examples for SSE, WebSockets, several coins, a WebSocket reconnect, a ticker in the page, and a small server route that keeps your key private. Prefer to let an AI tool write it? See how to build a real-time crypto portfolio tracker with Coinranking API and Replit.

1. Get an API key

Create a key at coinranking.com/api. Streaming is on the Professional plan, free for 30 days. A free key is refused with 403.

Do not put the key in a public page. The samples below include it so they run. Step 7 shows how to keep it on your server.

2. Server-Sent Events

SSE is the preferred option for a front end that only displays prices. The browser reconnects after a drop.

const API_KEY = "your-api-key-here";

const url =
  "https://api.coinranking.com/v2/real-time/sse/rates" +
  "?currency-uuids[]=Qwsogvtv82FCd" +
  "&currency-uuids[]=razxDUgYGNAdQ" +
  "&throttle=1s" +
  "&x-access-token=" + API_KEY;

const source = new EventSource(url);

source.onmessage = (event) => {
  const data = JSON.parse(event.data);
  console.log(data); // confirmation first, then rate messages
};

source.onerror = () => {
  // A dropped connection retries on its own.
  // A rejected request closes and does not retry.
};

Qwsogvtv82FCd is Bitcoin. razxDUgYGNAdQ is Ethereum. Pass more currency-uuids[] parameters for more coins, up to 100. list=all is the whole market, and it is high volume. Docs: Server-Sent Events.

3. WebSockets

Use a WebSocket when you need to change the subscription on the open connection.

const API_KEY = "your-api-key-here";

const ws = new WebSocket(
  "wss://api.coinranking.com/v2/real-time/rates?x-access-token=" + API_KEY
);

ws.onopen = () => {
  ws.send(JSON.stringify({
    currencyUuids: ["Qwsogvtv82FCd", "razxDUgYGNAdQ"],
    throttle: "1s" // 1s or 10s
  }));
};

ws.onmessage = (event) => {
  const data = JSON.parse(event.data);
  console.log(data);
};

ws.onclose = () => {
  console.log("WebSocket closed");
};

Docs: WebSockets.

4. Several coins

One connection takes at most 100 UUIDs.

const coins = [
  "Qwsogvtv82FCd", // Bitcoin
  "razxDUgYGNAdQ", // Ethereum
  "zNZHO_Sjf"      // Solana
];

const params = coins.map((uuid) => "currency-uuids[]=" + uuid).join("&");
const sseUrl =
  "https://api.coinranking.com/v2/real-time/sse/rates?" +
  params +
  "&x-access-token=" + API_KEY;

ws.send(JSON.stringify({
  currencyUuids: coins,
  throttle: "1s"
}));

5. Reconnect a WebSocket

SSE restores itself from the URL after a drop. A WebSocket does not remember the subscription.

let delay = 1000;

function connect() {
  const ws = new WebSocket(
    "wss://api.coinranking.com/v2/real-time/rates?x-access-token=" + API_KEY
  );

  ws.onopen = () => {
    delay = 1000; // connected again: reset the wait
    ws.send(JSON.stringify({
      currencyUuids: ["Qwsogvtv82FCd", "razxDUgYGNAdQ"],
      throttle: "1s"
    }));
  };

  ws.onmessage = (event) => {
    const data = JSON.parse(event.data);
    // handle data
  };

  ws.onclose = () => {
    // Wait longer after each failed try, up to one minute.
    setTimeout(connect, delay);
    delay = Math.min(delay * 2, 60000);
  };

  return ws;
}

let socket = connect();

Every new connection counts toward your rate limit, so the wait doubles after each failed try. A refused key or plan will not fix itself by retrying.

A reconnect does not replay missed rates. Load the current price from REST if the page cannot wait for the next tick. That pattern is the point of REST vs WebSocket for crypto prices.

6. A live ticker

A rate message uses currencyUuid, not uuid. The first message is a confirmation. Skip it.

<div id="btc-price">Bitcoin: loading...</div>
<div id="eth-price">Ethereum: loading...</div>

<script>
  const source = new EventSource(
    "https://api.coinranking.com/v2/real-time/sse/rates?currency-uuids[]=Qwsogvtv82FCd&currency-uuids[]=razxDUgYGNAdQ&x-access-token=YOUR_KEY"
  );

  source.onmessage = (event) => {
    const data = JSON.parse(event.data);
    if (data.type !== "rate") return;

    if (data.currencyUuid === "Qwsogvtv82FCd") {
      document.getElementById("btc-price").textContent =
        "Bitcoin: $" + Number(data.price).toLocaleString();
    }
    if (data.currencyUuid === "razxDUgYGNAdQ") {
      document.getElementById("eth-price").textContent =
        "Ethereum: $" + Number(data.price).toLocaleString();
    }
  };
</script>

7. Keep the key on your server

The samples above put the key in the page so they run. On a real site, a small route on your server adds the key and passes the stream to the browser. This is Node.js 18 or later, with no packages.

import http from "node:http";
import { Readable } from "node:stream";

// Only these coins can be streamed through this route.
const ALLOWED = new Set(["Qwsogvtv82FCd", "razxDUgYGNAdQ"]);

http.createServer(async (req, res) => {
  const url = new URL(req.url, "http://localhost");
  if (url.pathname !== "/stream/rates") return res.writeHead(404).end();

  const coins = url.searchParams.getAll("currency-uuids[]").filter((id) => ALLOWED.has(id));
  if (coins.length === 0) return res.writeHead(400).end();

  const upstream = new URL("https://api.coinranking.com/v2/real-time/sse/rates");
  coins.forEach((id) => upstream.searchParams.append("currency-uuids[]", id));
  upstream.searchParams.set("throttle", "10s");

  // Close the upstream stream when the browser tab closes.
  const controller = new AbortController();
  res.once("close", () => controller.abort());

  const response = await fetch(upstream, {
    headers: { "x-access-token": process.env.COINRANKING_API_KEY },
    signal: controller.signal
  }).catch(() => null);
  if (!response) return res.destroyed || res.writeHead(502).end();

  res.writeHead(response.status, {
    "content-type": response.headers.get("content-type"),
    "cache-control": "no-cache, no-transform"
  });
  Readable.fromWeb(response.body).on("error", () => res.end()).pipe(res);
}).listen(3000);

The browser now points at your own route, with no key in the page:

const source = new EventSource(
  "/stream/rates?currency-uuids[]=Qwsogvtv82FCd&currency-uuids[]=razxDUgYGNAdQ"
);

Turn off compression for this route, and set proxy_buffering off behind nginx. Otherwise the stream waits in a buffer. Each browser tab opens one upstream stream, so a busy site should share one stream across visitors. The SSE docs cover that.

Pitfalls

The API key in the browser is visible to every visitor. Proxy SSE through your own server.

EventSource cannot read a rejected response. A 401 or 403 closes the stream and does not retry.

Always JSON.parse(event.data), and ignore messages whose type is not rate.

Stay at or under 100 coins per connection. Use throttle=1s or throttle=10s if you do not need every tick.

SSE or WebSocket

SituationChoice
Price ticker on a websiteSSE
Portfolio dashboard with a fixed listSSE
Change coins without reconnectingWebSocket
Backend job that resubscribesWebSocket
Smallest clientSSE

The wider picture, including polling, topics, and limits, is in the complete guide to real-time crypto price streaming. Overview: real-time documentation. Key: coinranking.com/api.



Stay Updated

Join 30,000+ readers. Get the latest crypto news, analysis, and insights delivered to your inbox weekly. No spam, unsubscribe anytime.