WebSocket vs REST for market data

WebSocket and REST are the two ways market data reaches an application. WebSocket keeps one connection open and pushes every price update as it happens; REST answers individual requests for history, catalogs and snapshots. A production integration uses both: a stream for live ticks and REST for everything that has already happened. This guide shows how to combine them safely.

Updated 6 min read FinaQuotes engineering

Two delivery models

REST follows the request-response model of the web. The client sends an HTTPS request to a URL, the server returns one response and the exchange is over. Each request stands on its own, which makes REST easy to cache, easy to debug with standard tools and a good fit for questions with a definite answer: the list of instruments, the last 300 hourly candles, a logo.

WebSocket starts as an HTTPS request and then upgrades to a persistent, two-way connection. After that, either side can send messages at any time. For market data this means the client subscribes once and the server pushes each new tick the moment it is available, without the client asking again.

The difference is not about speed of a single message. It is about who decides when data moves. With REST the client decides, so it only learns about a price change when it next asks. With WebSocket the market decides, so the client learns about it straight away.

When to stream and when to request

A simple rule covers most cases: if the data changes on its own and you need to know when, stream it; if the data is a finished fact or changes rarely, request it.

  • Stream over WebSocket: live quotes in a terminal, a quote board, a ticker, the forming candle on a chart, price alerts, anything that must react to the market in under a second.
  • Request over REST: candle history for a chart, the instrument catalog with symbols and precision, logos, an occasional snapshot for a report or a page that is rendered once.
  • Avoid polling REST for live prices. Asking for the latest price every second for many instruments burns through rate limits, adds latency equal to half the polling interval on average, and still misses changes between requests.

How the two work together

A chart is the classic example of both protocols in one screen. On load, the application requests the visible range of candles over REST. It then opens a WebSocket subscription for the same instrument and updates the latest candle with each incoming tick. When the minute closes, the forming candle becomes history and a new one starts.

The FinaQuotes feed is built on this split. WebSocket carries the stream of ticks; REST serves candle history in 9 timeframes, the instrument catalog and logos. The REST reference and the main documentation describe both. How candles are formed, and what happens to the latest one, is covered in how candles are built from ticks.

AspectWebSocketREST
ModelServer push over one connectionClient request, one response
Typical useLive ticks, forming candle, alertsHistory, catalog, logos, snapshots
FreshnessSub-second after the provider receives a priceAs of the request
StateStateful: subscriptions live on the connectionStateless: each request is independent
Failure modeDisconnect, needs reconnect and backfillSingle request fails, retry it
LimitsNo per-message billingRate limits per IP, 429 when exceeded
CachingNot applicableEasy to cache history on your side

Connecting and authenticating

A streaming session usually goes through the same steps: open the connection, authenticate, subscribe to instruments, then process messages until the connection ends.

Authentication on the FinaQuotes WebSocket uses HMAC. The idea is simple. You receive a key identifier and a secret. To authenticate, your server computes a signature over a short message, typically including a timestamp, using the secret and a hash function such as SHA-256. It sends the key identifier and the signature, never the secret itself. The server repeats the calculation with its copy of the secret and accepts the session if the signatures match. Because the timestamp is part of the signed message, an old signature cannot simply be replayed later.

Two practical rules follow. Keep the secret on your server, in an environment variable or a secret store, never in browser code or a mobile app bundle. And keep your server clock synchronized, because a signature with a timestamp far from the server time can be rejected. The exact message format, field names and tolerances are in the documentation.

Reconnecting and backfilling the gap

Every long-lived connection eventually drops: a network change, a load balancer restart, a deploy on either side. A good client treats a disconnect as a normal event and recovers without a human.

  • Detect the drop. Listen for the close event, and use protocol-level ping and pong to notice a connection that is silently dead. Do not rely on the absence of ticks: a quiet instrument may not tick for a while.
  • Reconnect with backoff. Wait a short time, then retry, doubling the wait after each failure up to a cap, with a small random addition so that many clients do not reconnect in the same instant.
  • Authenticate and resubscribe. Subscriptions belong to the connection, so a new connection starts empty. Keep your subscription list on the client and send it again.
  • Backfill the gap. Remember the timestamp of the last tick you processed. After reconnecting, request candles over REST from that moment to now and merge them into your chart. Replace the last stored candle rather than appending a duplicate, since it may have changed while you were offline.
// Illustrative sketch. Message types and field names are placeholders;
// the real protocol is described in the documentation (/docs/).
import crypto from 'node:crypto';

const WS_URL = process.env.FEED_WS_URL;
const KEY_ID = process.env.FEED_KEY_ID;
const SECRET = process.env.FEED_SECRET; // never leaves your server

const sign = (ts) =>
  crypto.createHmac('sha256', SECRET).update(String(ts)).digest('hex');

let lastTickTime = null; // UTC timestamp of the last tick we processed
let attempt = 0;

function connect() {
  const ws = new WebSocket(WS_URL);

  ws.onopen = async () => {
    attempt = 0;
    const ts = Date.now();
    ws.send(JSON.stringify({ type: 'auth', key: KEY_ID, ts, sig: sign(ts) }));
    ws.send(JSON.stringify({ type: 'subscribe', symbols: ['EURUSD', 'XAUUSD'] }));
    // Close the hole left by the disconnect with candles from REST.
    if (lastTickTime) await backfillCandles(lastTickTime);
  };

  ws.onmessage = (event) => {
    const tick = JSON.parse(event.data);
    lastTickTime = tick.time;
    applyTick(tick);
  };

  ws.onclose = () => {
    // Exponential backoff with jitter, capped at 30 seconds.
    const delay = Math.min(30000, 1000 * 2 ** attempt++) + Math.random() * 1000;
    setTimeout(connect, delay);
  };
}

connect();

The sketch below shows the shape of such a client in JavaScript. It does not contain real endpoints or keys, and the message format is a placeholder.

Rate limits and HTTP 429

REST endpoints are shared resources, so they are rate limited. The FinaQuotes REST API applies reasonable limits per IP address, 120 requests per minute by default. When a client goes over the limit, the server answers with HTTP status 429 Too Many Requests instead of data.

Handle 429 as a signal to slow down, not as an error to retry immediately. Wait before the next attempt, honour a Retry-After header if the response carries one, and spread requests over time. Most limit problems disappear with two habits: cache history on your side, because past candles do not change once they are final, and load only the range the user is actually looking at.

The WebSocket stream is not billed per message, so a busy market does not raise your bill. The pricing page explains how access is priced.

Integration checklist

Before going live, check that your integration does the following.

  • Uses WebSocket for live prices and REST for history and the catalog, with no REST polling for quotes.
  • Keeps the HMAC secret on the server only.
  • Reconnects automatically with capped exponential backoff and jitter.
  • Resubscribes after every reconnect and backfills the gap from REST.
  • Treats 429 as back-off, caches history and stays within the documented limits.
  • Works in UTC end to end and converts to local time only for display.

Frequently asked questions

Can I get live prices over REST only?

Technically you can poll, but it is the wrong tool: polling adds latency, misses changes between requests and runs into rate limits. Use the WebSocket stream for live prices and REST for history and reference data.

Can I connect to the WebSocket directly from a browser?

Not with your secret. HMAC authentication requires the secret, and anything shipped to a browser is public. Connect from your server and relay prices to your front end, or use embeddable widgets where that fits.

What should I do after receiving HTTP 429?

Slow down and retry later. Wait before the next request, respect Retry-After if it is present, cache candle history and request only the range you need. The default limit is 120 requests per minute per IP; see the REST reference.

Need the feed behind these guides?

Tell us what you are building and which asset classes you need. We reply within one business day with access details and pricing.

Contact us