Real-Time World Cup Odds API: Catch Drops via SSE
Build a World Cup odds drop tracker with pinnapi's SSE stream. Pinnacle prices pushed sub-second, ~15-40 ms frame-to-client, with runnable code and filters.
Here is the number that decides everything else in this post: from a Pinnacle frame to your client, pinnapi delivers odds in roughly 15–40 ms, and those updates are pushed sub-second over MQTT/WebSocket and SSE — not polled. During a World Cup match, that gap is the whole game. A red card, a keeper limping off, sharp money landing on a total — the line falls in a heartbeat, and if you're polling every couple of seconds you find out after the market already moved.
So this is a guide to doing it the right way: detecting World Cup odds drops as they happen, using pinnapi's Server-Sent Events stream, with code you can paste and run.
Polling is the wrong tool for live markets
Polling a REST endpoint every N seconds carries two costs that compound during in-play action.
The first is a hard latency floor. Poll every 2 seconds and your worst-case staleness is 2 seconds plus round-trip — and the moment that matters, a price collapsing during a goal-mouth scramble, almost always lands inside that blind window. The second cost is quieter: most polls return data that hasn't changed. You spend rate limit and bandwidth to confirm that nothing happened.
An SSE odds stream flips the relationship. You hold one long-lived HTTP connection open, and the server emits an event only when a price actually moves. For comparison, polled aggregators run seconds stale, and even other push feeds typically land around 200 ms or more. Against that, frame-to-client in the 15–40 ms range (may vary by region and plan) means the drop event reaches your handler close to the instant the market turns. That's the difference between reacting and reading about it.
What a "drop" actually is
A drop is a market's price falling against its recent history. In betting terms the implied probability went up — the market now rates the outcome more likely, so the payout shrank. Drops carry signal because they tend to cluster around information the wider market is still pricing in.
pinnapi surfaces drops two ways, and you'll use both:
GET /api/drops?mode=live— a REST snapshot of recent drops. Good for dashboard refreshes and, critically, recovery after a disconnect.- The SSE drop stream — a continuous push of drop events as they fire. This is the engine.
The REST /api/drops endpoint and the live /kit/v1/markets board are available on the free trial key, which you get in seconds — the key is your login, no card, no email verification. Trials are capped at 100 REST requests per day, plenty to prototype and validate your drop logic. SSE drop streaming itself is a paid feature; the pricing tiers start at $99/mo and unlock the stream.
Step 1: get a key, confirm the feed
Authenticate with the x-portal-apikey header or a ?key= query param. Sanity-check connectivity before anything else:
curl -s https://pinnapi.com/health \
-H "x-portal-apikey: YOUR_KEY"
Then pull the live board. Don't hardcode fixtures — the tournament is live, event IDs change, and the API is the source of truth. Discover the current soccer events at runtime:
curl -s "https://pinnapi.com/kit/v1/markets?event_type=live" \
-H "x-portal-apikey: YOUR_KEY"
Find your World Cup event in the response, note its identifier, and you're ready to stream. Every parameter is in the API reference.
Step 2: connect to the drop stream
SSE is just HTTP — any client that reads a streaming response will do. Here's a minimal Node consumer that listens for live drops:
import { EventSource } from "eventsource";
const KEY = process.env.PINNAPI_KEY;
const stream = new EventSource(
"https://pinnapi.com/api/drops?mode=live",
{ headers: { "x-portal-apikey": KEY } }
);
stream.onmessage = (event) => {
const drop = JSON.parse(event.data);
// drop carries the market, old/new price, and magnitude
handleDrop(drop);
};
stream.onerror = (err) => {
console.error("stream error, will auto-reconnect", err);
};
function handleDrop(drop) {
const movePct = ((drop.previous - drop.current) / drop.previous) * 100;
if (movePct >= 3) {
console.log(
`DROP ${drop.eventId} ${drop.market}: ` +
`${drop.previous} -> ${drop.current} (${movePct.toFixed(1)}%)`
);
}
}
The socket stays open. When a market drops, the event lands sub-second after the move — no polling loop, no wasted calls.
Step 3: cut the noise down to signal
A live match throws off a lot of micro-movement, and most of it isn't tradable. The real work is filtering. Three filters do most of the lifting, and they're not equally important.
Magnitude is the one that matters most. Set a floor and ignore everything under it — the example uses 3%. Below that you're mostly watching jitter, and jitter will wake you up at 3am for nothing.
After that, scope and velocity refine the picture. Subscribe to the markets you actually trade — match result, totals, Asian handicap — and discard the rest in your handler. Then watch the rhythm: three drops on the same market inside ten seconds is a far louder signal than one drop in five minutes, because clustered moves usually mean the market is repricing on fresh information rather than drifting.
A small velocity check:
const lastSeen = new Map();
function isFastDrop(drop) {
const key = `${drop.eventId}:${drop.market}`;
const now = Date.now();
const prev = lastSeen.get(key);
lastSeen.set(key, now);
return prev !== undefined && now - prev < 10000; // within 10s
}
Worked example: say a totals line on a group-stage match sits at 1.95 and ticks to 1.90 — that's a ~2.6% move, under your floor, so handleDrop stays quiet. Seconds later it goes 1.90 → 1.82 (~4.2%), clears the threshold, and isFastDrop returns true because it's the second move inside ten seconds. That combination — magnitude past the floor plus velocity — is the handful of events worth acting on, pulled out of the hundreds your stream will see in a half.
Reconnects: stream for speed, REST for recovery
Long-lived connections die. Networks hiccup, load balancers cycle, laptops sleep. Build for it rather than pretending it won't happen.
Most SSE clients reconnect on their own, so the work that's on you is what happens after the reconnect. Treat the first events as possibly stale, and call GET /api/drops?mode=live once to backfill anything you missed while offline before you trust the live stream again. Keep a separate lightweight liveness check against /ping so you can tell "the data went quiet" apart from "the connection died." That belt-and-suspenders pattern keeps your view of the market consistent even on a flaky hotel Wi-Fi connection during a late kickoff.
Drop-in compatibility, so this is an afternoon not a sprint
If you already run Pinnacle-style client code, you don't rewrite it. Swap the base URL to pinnapi.com, set your x-portal-apikey, and your existing market-parsing logic keeps working. You inherit the Pinnacle live odds API feed plus the SSE drop layer on top of code you've already tested. A World Cup 2026 drop tracker becomes an afternoon's work.
Prematch and live are the same fixture, different phases
Drops aren't only an in-play thing. The hours before a marquee fixture often see the heaviest movement as team news drops and money flows in. pinnapi covers both ends:
- Prematch:
/kit/v1/prematch/fixtures,/kit/v1/prematch/markets,/kit/v1/prematch/lines, plus/api/drops?mode=prematch. - Live:
/kit/v1/markets?event_type=liveand/api/drops?mode=liveonce the whistle goes.
Run a prematch watcher in the run-up to kickoff, then switch your stream to mode=live at the whistle and follow the same market end to end.
The takeaway
For something as volatile as a World Cup match, push beats poll — not marginally, but by the seconds that hold the edge. Open one SSE connection to the drop stream, filter hard on magnitude first and velocity second, and back the whole thing with a REST snapshot for recovery. Start by mapping the live board with /kit/v1/markets, validate your drop logic against /api/drops on a trial key, and flip on streaming once the filter is doing what you want. With frame-to-client in the 15–40 ms range, the limiting factor stops being your feed and becomes your logic — which is exactly where you want it.
Frequently asked questions
How fast does pinnapi deliver World Cup odds?
Frame-to-client latency runs about 15–40 ms from a Pinnacle frame to your client, and updates are pushed sub-second over MQTT/WebSocket and SSE rather than polled. Polled aggregators are seconds stale; other push feeds typically land around 200 ms or more. Figures may vary by region and plan.
What is an odds drop and how do I detect it?
A drop is a market's price falling against its recent history, meaning implied probability rose. Detect them via the SSE stream at /api/drops?mode=live for live push events, or poll GET /api/drops for a snapshot. Filter by magnitude (e.g. ignore moves under 3%) and velocity to surface the signal.
Can I use the SSE drop stream on the free trial?
No. The free trial includes REST endpoints like /api/drops and live /kit/v1/markets, capped at 100 requests per day, which is enough to prototype drop logic. The SSE drop streams are a paid feature; plans start at $99/mo.
Do I have to rewrite my existing Pinnacle client code?
No. pinnapi is drop-in compatible. Swap the base URL to pinnapi.com and set the x-portal-apikey header or ?key= query param. Your existing market-parsing logic keeps working, and you add the SSE drop layer on top.
How should I handle SSE disconnects during a match?
Let the client auto-reconnect, then call GET /api/drops?mode=live once to backfill any drops missed while offline before trusting the live stream again. Use /ping as a separate liveness check to distinguish a quiet market from a dead connection.
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