Insights

XRPL WebSocket API: Real-Time Token Prices & Market Data

Instead of asking the API for prices every few seconds, open one WebSocket and xrpl.to sends each change as the ledger produces it. The streams there are, a subscription that works, and what actually comes back.

The streams

/ws/sync/Prices and market data for a list of tokens, or the top of the market by volume
/ws/token/{md5}Every change to one token, optionally only the fields that changed
/ws/ohlc/{md5}Candles as they form, at 1 minute to 1 week
/ws/history/{md5}Each trade in a token as it fills
/ws/orderbookOrder book depth for a pair
/ws/ledgerEach ledger as it closes
/ws/account/watch/{account}An account’s activity, as readable events
/ws/newsNews stories as they are published
All at wss://api.xrpl.to; the full list, with each stream’s options, is in the API docs.

Subscribing to prices

const ws = new WebSocket('wss://api.xrpl.to/ws/sync/');

ws.onopen = () => {
  ws.send(JSON.stringify({
    type: 'subscribe',
    tokens: ['0dd550278b74cb6690fdae351e8e0df3'], // token md5s
    lightweight: true,                            // the essential fields only
  }));
};

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);
  if (msg.type === 'subscribed') return;          // the acknowledgement
  if (msg.snapshot) render(msg.tokens);           // current state, once
  else if (msg.seq) update(msg.tokens, msg.seq);  // changes; may be empty
};
// Reconnect with growing delays when the socket closes.
Request
{
  "type": "subscribe",
  "tokens": [
    "0dd550278b74cb6690fdae351e8e0df3"
  ],
  "lightweight": true,
  "skip_metrics": true
}
Response
{
  "res": "success",
  "snapshot": true,
  "seq": 1501,
  "time": 1792387911108,
  "count": 1,
  "tokens": [
    {
      "md5": "0dd550278b74cb6690fdae351e8e0df3",
      "name": "RLUSD",
      "exch": 0.762471760532176,
      "pro24h": -1.4321792493104502,
      "vol24hxrp": 3092792.208121,
      "marketcap": 777906798.0382205,
      "holders": 67589,
      "tvl": 3687370.976908
    }
  ]
}
A real snapshot frame, captured when this page last refreshed, trimmed to a few of the token’s fields: exch is the price in XRP, pro24h the 24-hour change in percent.

What arrives, in order

  1. A welcome frame on connecting, with the market’s totals.
  2. The acknowledgement, type: "subscribed", echoing the md5s you asked for.
  3. A snapshot of every subscribed token as it stands (skip it with snapshot: false).
  4. Updates with a seq number and the tokens that changed; a quiet market sends empty ones now and then, as a heartbeat.

Instead of a list, you can subscribe to the top of the market with filter: { topN, minVolume, tags }, and change the list later with addTokens and removeTokens.

Staying connected

  • Gaps: compare each update’s prevSeq with the last seq you saw, and send resync if they differ.
  • Close codes say why: 4010 rate limit, 4011 too many connections or subscriptions, 4012 message too large, 4020–4025 a bad parameter.
  • Credit the data: an app that shows xrpl.to data shows a visible “Data by xrpl.to” link (API terms).

For one-off reads use the REST API instead (API guide); for prices in bulk, the price API.

Questions

Does the xrpl.to WebSocket need an API key?
No. Public streams work without one at anonymous limits; send { "type": "auth", "apiKey": "…" } on the socket to use your key’s limits.
How do I know I missed an update?
Each update carries seq and prevSeq. If prevSeq is not the last seq you saw, ask the socket to resync and it sends the current state again.
How do I keep the connection alive?
Send { "type": "ping" } and the server answers with a pong. When it does close, reconnect with a growing delay.