Low-Latency Pinnacle Odds: Push Beats Polling

Low latency odds api explained: why a pushed WebSocket/SSE feed delivers Pinnacle line moves sub-second while polling REST leaves you a poll-interval behind.
Short version: polling a REST endpoint on a timer means you are, on average, half a poll-interval behind every line move — and you pay for a pile of requests that return nothing new. A pushed feed (WebSocket or SSE) hands you the change the instant the market moves. If you are doing drop detection or closing-line work, that gap is the whole game.
This is an engineering post, not a pitch. I want to be precise about where latency comes from, which parts you can control, and which you can't. pinnodds is a pushed Pinnacle feed, so I have an obvious bias — but the physics here are the same whatever service you use, and I will say plainly where polling is the right call.
The latency polling adds, quantified
Say Pinnacle moves a soccer total from 2.5 to 2.75. With polling, here is the timeline:
- The market moves at time
T. - Your next scheduled request fires at some point after
T. - That response is the first time your code sees the new price.
If you poll every 5 seconds, and moves land uniformly across that window, your average added delay is ~2.5 seconds and your worst case is just under 5 seconds — a move that happens one millisecond after a request sits invisible until the next one. Tighten the loop to 1 second and you cut that to ~0.5s average, 1s worst case, but now you are making 60 requests a minute per market slice, most of which return "nothing changed." Tighten it further and you hit rate limits, waste bandwidth, and load the upstream for no signal.
That is the core problem with polling: the staleness and the request cost trade directly against each other, and neither ever reaches zero. You are always choosing a compromise interval.
A pushed feed collapses that trade. There is no interval. The server holds an open connection and writes to it the moment a change lands, so the poll-interval term drops out of the equation entirely. What remains is only the latency you can't design away.
The latency that's real no matter what
Push removes the artificial wait. It does not make the network faster or the upstream feed instantaneous. Being honest about the remaining sources:
- Upstream feed. The odds have to reach us before they can reach you. That is the biggest single factor and it is not something any reseller magics away — a pushed feed just means we forward it the instant we have it instead of holding it for your next poll.
- Network path. TLS, the hop count between you and the edge, and your distance from the origin all cost milliseconds. A persistent connection helps here too: no repeated TCP + TLS handshake per update, which is dead weight polling pays on every single request.
- Your own consumer. This one surprises people. If your handler does synchronous work — a slow JSON parse, a blocking DB write, a downstream HTTP call — inside the message callback, you serialise processing and build a backlog. A fast feed feeding a slow consumer is a slow pipeline. Keep the hot path lean: parse, enqueue, return; do the heavy work off the socket thread.
None of these have a fixed millisecond figure I can promise you, and I distrust anyone who quotes one. What a pushed model does guarantee is that we add no poll-interval staleness on our side — you get the move as soon as we do.
Polling vs push, in code
Here is the polling shape. Note the interval you are stuck picking, and the fact that most iterations do nothing:
// Polling: you're always up to POLL_MS behind, and most calls change nothing
const POLL_MS = 5000;
const seen = new Map(); // event_id -> last price snapshot
async function pollOnce() {
const res = await fetch("https://pinnodds.com/kit/v1/markets", {
headers: { "x-api-key": process.env.PINNODDS_KEY },
});
const markets = await res.json();
for (const m of markets) {
const prev = seen.get(m.event_id);
if (prev && prev !== JSON.stringify(m)) {
// reacting up to POLL_MS after the move actually happened
handleChange(m);
}
seen.set(m.event_id, JSON.stringify(m));
}
}
setInterval(pollOnce, POLL_MS);
And here is the pushed equivalent for drop alerts over SSE. There is no interval, no diffing, no "did anything change" bookkeeping — the server only speaks when something moved:
import EventSource from "eventsource";
// Push: fires the instant a line moves, nothing in between
const es = new EventSource(
`https://pinnodds.com/odds-drop?key=${process.env.PINNODDS_KEY}`
);
es.onmessage = (e) => {
const d = JSON.parse(e.data);
// sect = market, outcome = moving selection,
// nvp = no-vig fair price, limit = Pinnacle max stake
console.log(
`${d.id} ${d.sect} ${d.outcome}: ${d.from_price} -> ${d.to_price} (nvp ${d.nvp})`
);
// keep this callback cheap; hand off heavy work to a queue
};
es.onerror = () => {
// EventSource auto-reconnects; on reconnect, reconcile via REST (below)
};
The two blocks do superficially the same job — notice a price change — but the second one is never behind by a poll interval, never burns a request on an unchanged market, and never makes you tune a cadence.
For the raw markets stream rather than just drops, the same logic applies to the WebSocket passthrough at /ws/feed — full field reference and connection details are in the WebSocket API guide and the docs.
Why REST still matters — reconciliation and the drop percentage
Push is the fast path. It is not the complete path, and pretending otherwise gets people burned.
Two reasons you still want REST alongside the stream:
1. Reconciliation after a gap. Any long-lived connection drops sometimes — a redeploy on your side, a flaky mobile network, a proxy timeout. While you were disconnected, moves happened. When EventSource reconnects, you query REST to backfill the current state rather than assuming the stream caught you up. A stream-only pipeline silently develops blind spots; a stream-plus-reconcile pipeline stays honest under load.
2. The enriched drop number. The SSE alert gives you sect, outcome, from_price, to_price, id, plus limit and nvp — but no drop percentage. REST /api/drops (mode=live|prematch) is the enriched form: market, designation, from, to, event_id, and a precomputed drop_pct. So the pattern is: react on the stream for speed, then read REST when you want the tidy percentage or the settled state.
// After a drop alert (or a reconnect), pull the enriched, percentaged view
const res = await fetch("https://pinnodds.com/api/drops?mode=live", {
headers: { "x-api-key": process.env.PINNODDS_KEY },
});
const drops = await res.json();
// each row: market, designation, from, to, event_id, drop_pct
Treat SSE and REST as two views of one event, keyed on id / event_id. Stream for the when, REST for the how much and the catch-up.
When polling is genuinely the right call
I am not going to tell you push wins every time. It doesn't.
- Slow prematch markets days out from kickoff barely move. Polling every 30–60 seconds is completely adequate and simpler to operate — no persistent connection to babysit.
- Batch or analytical jobs that snapshot the board on a schedule don't need push at all. A single REST pull is the natural fit.
- You're on the REST-only tier. Push (SSE) lives on the $149/mo Pro + SSE plan and above; entry Pro is REST. If a polling loop meets your latency budget, there is no reason to pay for the stream. Be honest with yourself about how fast your markets actually move before you buy speed you won't use. See pricing.
The decision rule is simple: match the delivery model to how fast your markets move and how quickly you must react. In-play and near kickoff, where lines move in seconds and closing-line value is won or lost fast, push earns its keep. Deep prematch, it often doesn't.
Takeaway
Latency is a stack, not a number. Polling bolts an avoidable term onto the top of that stack — up to a full poll interval of staleness, plus a request bill for updates that mostly say "nothing changed." A pushed feed removes exactly that term and leaves you with the honest, irreducible costs: upstream, network, and your own consumer. That is why, for drop detection and closing-line work, we push by default at /ws/feed and /odds-drop — and why we still keep REST /api/drops right next to it for the drop_pct and for reconciling whatever a disconnect ate. Wire the stream for speed, back it with REST for truth, and don't pay for push on markets that crawl. Grab a free key from the homepage and time it against your current polling loop — that comparison costs you an hour and settles the argument.
Frequently asked questions
What makes an odds API low-latency?
Delivery model, mostly. A pushed feed (WebSocket or SSE) sends a line change the instant the market moves, so the only delay is network plus your own processing. Polling a REST endpoint on a timer adds up to your whole poll interval of staleness on top of that, because you only learn about a move on the next request.
How much latency does polling actually add?
On average about half your poll interval, and in the worst case the full interval. Poll every 5 seconds and a move that lands just after a request sits unseen for nearly 5 seconds. A pushed feed removes that waiting window entirely — there is no interval to wait out.
Does pinnodds deliver Pinnacle odds over WebSocket?
Yes. pinnodds offers a raw WebSocket passthrough at /ws/feed and Server-Sent Events at /odds-drop (live) and /odds-drop-prematch (prematch). Both push changes as they happen rather than making you poll. Push access (SSE) is on the $149/mo Pro + SSE plan and above.
If push is faster, why does pinnodds still have a REST API?
Reconciliation and enrichment. REST /api/drops carries a precomputed drop_pct that the SSE alert does not, and REST is how you backfill anything a stream missed during a disconnect. Stream for speed, query REST for the tidy number and to catch up. They are two views of one event keyed on id / event_id.
Is pinnodds latency guaranteed to be a specific number of milliseconds?
No, and anyone quoting you a fixed figure is guessing. Real latency depends on the upstream Pinnacle feed, network path, and your own consumer. What a pushed feed guarantees is that no poll-interval staleness is added on our side — you get the move as soon as we do.
Does low latency matter for prematch, or only in-play?
Both, but it bites hardest in-play and near kickoff, where lines move fast and closing-line value is decided in seconds. For slow prematch markets a polling loop is often fine. Match the delivery model to how fast your markets actually move.
Get real-time Pinnacle odds in your code
The Pinnacle APIby pinnodds: live & prematch markets with instant odds-drop alerts over SSE and WebSocket. See plans and pricing, or get a free key in seconds — no card, no Pinnacle account.
Get your free API key