A Solana price WebSocket removes the polling interval from a live ticker. It does not remove indexing delay, network delay, or the need to recover after a disconnect. Build the ticker around a last-known value and an explicit freshness state.
Aggregate price or pool price?
Use subscribe.price.aggregated(mint) for the aggregate feed. Read update.aggregated.median from its payload. Use subscribe.price.pool(pool) when the screen represents one specific pool. Do not silently switch between those definitions in a chart.
Run the Node.js example
Use Node.js 24 LTS and keep the API credentials on your server. Create an empty directory, then install:
npm init -y
npm install @solana-tracker/[email protected]
npm install --save-dev tsx typescript @types/node
Save the TypeScript block as index.mts. Put ST_API_KEY in an uncommitted .env file. Streaming examples also need ST_DATASTREAM_URL, the full private WebSocket URL from your Data API dashboard. REST and streaming access depend on your plan.
node --env-file=.env --import tsx index.mts
import { Datastream } from '@solana-tracker/data-api';
const wsUrl = process.env.ST_DATASTREAM_URL;
if (!wsUrl) throw new Error('Set ST_DATASTREAM_URL from your dashboard');
const ds = new Datastream({ wsUrl });
ds.on('error', () => console.error('Datastream connection error'));
process.once('SIGINT', () => ds.disconnect());
const mint = process.env.TOKEN_MINT ?? 'So11111111111111111111111111111111111111112';
const subscription = ds.subscribe.price.aggregated(mint);
const listener = subscription.on((update) => {
const price = update.aggregated.median;
if (!Number.isFinite(price)) return;
console.log({ mint: update.token, price, receivedAt: Date.now() });
});
ds.on('disconnected', () => console.log('Price feed stale'));
process.once('SIGTERM', () => {
listener.unsubscribe();
ds.unsubscribe(subscription.room);
ds.disconnect();
});
Subscribing starts the SDK connection. The SDK responds to its JSON heartbeats and reconnects automatically by default. Adding a second setTimeout(() => ds.connect()) loop creates competing connection lifecycles.
Load a snapshot without overwriting a newer tick
A practical startup sequence is to subscribe, buffer updates briefly, request a REST price snapshot, then apply the buffered updates. This avoids an uncovered interval between loading history and opening the stream. Do not let a slow REST response replace a more recent live tick.
Receipt time tells you when your process heard an event. It is not proof of when the market changed. Preserve source timestamps where supplied and define a product-specific stale threshold. An inactive token can legitimately have no new trades; show connection health separately from quote age.
Recover and clean up subscriptions
After reconnecting, refresh REST state and reconcile it with buffered live updates. A successful reconnection does not establish that every update during the outage was replayed.
Keep one subscription per active mint in a registry. When the last viewer leaves, remove the callback and leave the server room. Listener unsubscribe() only removes that callback; ds.unsubscribe(room) leaves the room. disconnect() closes the connection and clears subscriptions.
For a browser app, forward the needed prices through your own backend. The full Datastream URL contains a credential and belongs outside client bundles.
FAQ
Do I still need REST?
Yes. Use it for the initial price and recovery after connection gaps. See single and batch prices.
Does a price tick include a trade?
No. Use the trade stream for wallet, direction, and trade amounts.
Can I subscribe to several tokens?
Yes. Maintain separate subscriptions and check current connection and subscription limits in your dashboard.
References
Companion project
The companion example contains a fuller project layout. The code in this article targets SDK 0.5.0; check the companion package version before combining examples. Keep credentials in a local environment file, never in a shared browser workspace.