A working WebSocket integration has three distinct milestones: an authenticated connection, an accepted subscription and an actual quote update. Test each milestone independently so your UI does not report live data merely because a socket opened.
Generate a streaming credential on your server
Call POST /api/token/generate with your API key. The response provides token, expiresAt, wsUrl and sseUrl at the top level. expiresAt is an epoch timestamp in milliseconds. Lifetime and access follow the active plan; do not hard-code a duration.
curl --fail-with-body "$NAVQUOTE_API_BASE/api/token/generate" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
--data '{}'
Keep this operation behind your own authenticated server route. Return only the short-lived connection details needed by an authorized client. Your server should restrict who can mint tokens and confirm the returned endpoint belongs to the intended service before forwarding it. The proposed neutral WebSocket endpoint is wss://stream.navquote.com/ws, pending live validation.
Subscribe and distinguish message types
The browser helper below takes the successful token response as connection. It does not implement your server’s token route or a reconnect policy. Call connectQuotes(connection, onQuote) after your application has obtained and validated those details. Retain the returned socket to close it when the component unmounts.
function connectQuotes(connection, onQuote) {
if (!connection.success || !connection.token || !connection.wsUrl
|| !Number.isFinite(connection.expiresAt)
|| connection.expiresAt <= Date.now()) {
throw new Error("A valid streaming credential is required");
}
const url = new URL(connection.wsUrl);
url.searchParams.set("token", connection.token);
const socket = new WebSocket(url);
socket.addEventListener("open", () => {
socket.send(JSON.stringify({
action: "subscribe_quote",
symbols: ["NASDAQ:AAPL"],
fields: "all",
}));
});
socket.addEventListener("message", event => {
let message;
try { message = JSON.parse(event.data); }
catch { console.warn("Unrecognized stream message"); return; }
if (message.type === "quote_subscribed") {
console.info("Subscription accepted; waiting for market data");
} else if (message.type === "quote_update") {
onQuote(message);
} else if (message.type === "error") {
console.warn("Stream reported an error; inspect application diagnostics");
}
});
socket.addEventListener("error", () => console.warn("Stream transport error"));
socket.addEventListener("close", () => console.info("Stream disconnected"));
return socket;
}
A quote update contains a symbol and a data object. Inspect available quote fields such as lp and lp_time before rendering. Updates can omit fields, so do not replace a complete local record with an unchecked partial object. The envelope timestamp describes message emission, not necessarily the underlying last trade.
Define a connection state machine
Use explicit states such as connecting, subscribed, receiving, disconnected and stopped. Keep the desired subscriptions outside the socket instance. On a transient disconnect, wait with bounded backoff and jitter, acquire a usable credential if necessary, then open a new connection and subscribe again.
Stop reconnecting when the user leaves the screen or signs out. Surface authorization failures instead of endlessly minting tokens. Record retry counts and error categories without recording credential-bearing URLs. This sample intentionally ends at a single connection; a production reconnect manager needs application-specific cancellation and retry budgets.
Recover current state after a gap
No gap-free event replay guarantee has been verified. After a disconnect, mark values as potentially stale and reconcile with a REST quote snapshot if your product needs current state. Do not claim a complete tick history from the updates you happened to receive.
Before using the connection in your app, test successful subscription, an unavailable symbol, token expiry, a forced network close and user cancellation. A heartbeat or acknowledgement alone should never count as a successful market-data result.