Streaming Pinnacle Odds over WebSocket: When You Need It Instead of SSE
A practical comparison of WebSocket vs SSE for live Pinnacle odds: how each behaves, when the extra complexity of WebSocket is worth it, and a working client.
Streaming Pinnacle Odds over WebSocket: When You Need It Instead of SSE
We offer live odds over three transports — REST, SSE, and WebSocket — and the most common question we get is "which streaming one should I use?" The honest answer is that most people should use SSE, and WebSocket is for a specific minority of cases. This post is about telling which one you're in, so you don't take on complexity you don't need.
If you haven't read the SSE walkthrough yet, start there; this assumes you know the basic streaming model.
The one-sentence difference
SSE is a one-way stream: the server pushes events to you over a plain HTTP connection. WebSocket is a two-way channel: you can also send messages up to the server mid-connection. For odds data — which flows server → client — that upstream channel is the entire reason to choose WebSocket, and most consumers never use it.
And don't forget the third option: plain REST polling. If your use case is periodic snapshots — prematch scans every few minutes, model recalibration, a dashboard — polling a snapshot endpoint is simpler than either stream and completely adequate. The websocket-vs-REST question only really exists once you need to react to changes as they happen; at that point your polling interval becomes your latency floor, and push transport — either one — wins.
When SSE is the right choice (probably you)
Pick SSE if:
- You subscribe to a fixed set of sports/markets at connect time and don't change it often.
- You want the simplest possible client. SSE is just an HTTP request;
EventSourcehandles reconnects and event IDs for you. - You're behind infrastructure that's friendlier to HTTP than to long-lived WebSocket upgrades (some proxies, some serverless platforms).
For the large majority of "stream me the drops" use cases, SSE is less code, fewer failure modes, and identical data.
When WebSocket earns its complexity
Pick WebSocket if you genuinely need to talk back to the server on the same connection:
- Dynamic subscriptions. You want to subscribe and unsubscribe from specific fixtures while connected — say, follow a match the moment it goes live and drop it at full time — without tearing down and reopening a stream.
- Very high fan-in across many fixtures where re-establishing SSE connections to change scope would be costly.
- Bidirectional control messages (heartbeats you initiate, server-side filtering you adjust live).
If none of those apply, WebSocket is just SSE with more ways to break. If they do apply, the product side (what the raw feed carries, what it costs, which plans it sits on) is on the Pinnacle WebSocket API page; this post stays on the how.
A working WebSocket client
import WebSocket from "ws";
const ws = new WebSocket("wss://pinnapi.com/ws/feed", {
headers: { "x-portal-apikey": process.env.PINNAPI_KEY },
});
ws.on("open", () => {
// the upstream channel: subscribe dynamically (sport_ids use pinnapi sport IDs, 1 = soccer)
ws.send(JSON.stringify({ type: "subscribe", streams: ["live"], sport_ids: [1] }));
});
ws.on("message", (raw) => {
const msg = JSON.parse(raw);
switch (msg.type) {
case "connected":
case "subscribed":
case "ping": return; // control frames
case "snapshot": return seedState(msg); // initial state for a subscription
default: handleFrame(msg); // a forwarded raw Pinnacle frame
}
});
ws.on("close", () => scheduleReconnect());
ws.on("error", (e) => console.error("ws error", e));
The subscribe message is the part SSE can't do cleanly. Later, { type: "unsubscribe", streams: ["live"], sport_ids: [1] } (or event_ids: [...]) narrows your stream without a reconnect.
Reconnection is on you (this is the real cost)
With SSE, EventSource reconnects automatically. With WebSocket, you own the reconnect loop — and getting it wrong is the most common WebSocket bug:
let backoff = 500;
function scheduleReconnect() {
setTimeout(() => {
backoff = Math.min(backoff * 2, 15_000); // exponential, capped
connect(); // your connect() re-auths + re-subscribes
}, backoff);
}
Two things people forget: re-subscribe after reconnect (the server doesn't remember your subscriptions across a dropped socket), and resync state with a REST snapshot rather than assuming you didn't miss events during the gap. Same recovery principle as SSE — stream for speed, REST for truth.
Latency: basically the same
People assume WebSocket is faster than SSE. For server→client push of small JSON events, the difference is negligible — both ride a warm TCP connection and the payload is tiny. Don't choose WebSocket for speed; choose it for the upstream channel. If latency is your concern, the thing that actually moves the number is region and load, not transport — measure it from your own region and infrastructure.
Takeaway
Default to SSE. Reach for WebSocket only when you need to change your subscription live or otherwise send messages upstream — and when you do, budget for owning the reconnect-and-resubscribe logic yourself. The data and the latency are the same; what differs is how much machinery you're signing up to maintain.
Frequently asked questions
Is WebSocket faster than SSE for odds?
Not meaningfully for server→client push of small events. Choose by feature need (bidirectional), not speed.
Can I change which fixtures I'm watching without reconnecting?
That's exactly what WebSocket is for — send a subscribe/unsubscribe message on the open connection. SSE can't do this cleanly.
What happens to my subscriptions if the socket drops?
They're gone — re-subscribe on reconnect and resync state with a REST snapshot.
Get real-time Pinnacle odds in your code
Live & prematch markets with sub-second odds-drop alerts. Free trial key in seconds — no card.
Start free trial