Pinnacle odds over WebSocket vs REST polling
A WebSocket stream of Pinnacle odds pushes every price change to your bot as it happens, about 30 ms after Pinnacle publishes it for a bot hosted in Europe, on one connection that covers every sport you subscribe to. REST polling pulls the current book only when you ask, so your prices are as old as your last request, and each request spends part of a rate limit. Use the stream for in-play, for many events at once and for bots that act on price moves; REST is enough for pre-match, a handful of events and dashboards.
This page puts numbers on that choice: the latency we measured on the stream, what polling the same book would cost in requests, and how a stream client keeps its prices correct. The step-by-step clients are in Python and Node.js.
1. WebSocket vs REST for odds: push vs pull at a glance
With push, the server sends you each change the moment it has it. With pull, you ask, and get the state at the moment of the request. Both read the same prices: the difference is when they reach you, and what it costs to keep up.
| WebSocket (push) | REST (pull) | |
|---|---|---|
| How fresh | Every change, about 30 ms after Pinnacle publishes it (Europe) | As fresh as your last request |
| Changes between two looks | Each one is pushed, with its version | Only the last state; moves in between are lost |
| Cost per update | None: the server sends it | One request per event, every time |
| Limit | One connection, 1,000 single events or whole sports | 120 requests at once, then 300 a minute |
| State you keep | A book: snapshot plus updates by version | None needed: each answer is the current book |
| Plan | REST + WebSocket, $138 a month | REST, $69 a month (also in REST + WebSocket) |
2. What a real-time Pinnacle odds stream gives you
On the stream you log in once, subscribe to sports or single events, and from then on every update arrives as
an odds message with Pinnacle's record in data and the time the price was published
in data.ts. You do not ask again: a quiet market costs nothing, and a market that moves five times in a
second reaches you five times, each with a higher version.
How long that takes we measured on November 6, 2026, with a paying client connected through the public
endpoint, wss://feed.oddspush.com/v1/stream, for five minutes, following soccer, tennis, basketball and hockey. The
figure is end to end: from the publish time in data.ts to the moment the message reached the bot.
| Where the bot runs | p50 | p90 | p99 |
|---|---|---|---|
| Bot in Ireland | 24 ms | 36 ms | 71 ms |
| Bot in Spain | ~46 ms | ~77 ms | ~140 ms |
Same feed, same day: the difference between the two rows is the network between Ireland and the bot. That
part is yours to choose: host your bot close to Ireland (AWS eu-west-1 or nearby) for the lowest
latency. Further away, the median grows with the distance, and the tail with the quality of the line.
The stream also tells you when something is wrong instead of leaving you with stale prices: a
keepalive every 15 s of idleness, feed "down" and
"up" when the feed drops and comes back, and missed when your bot falls
behind (other messages).
3. What REST polling costs: freshness and requests
GET /v1/events lists the events and how many open markets each one has per channel, but no
prices. The prices of one event come from GET /v1/events/{event_id}
(reference). So a polled book costs one request per event, every time you
refresh it. The rate limit is per account: up to 120 requests at once, then 300 a minute,
which is 5 a second (REST). Over it you get a 429 with
retry-after in seconds.
Three numbers follow from that:
- Freshness is half your interval, on average. A price changes at a random moment between two polls, so with a poll every 10 s you see it on average 5 s late, and up to 10 s late. Against a median of 24 ms on the stream, that is about 200 times slower.
- 50 events every 10 s use the whole budget. 5 requests a second times 10 s is 50 requests: nothing left for anything else.
- A full pass over the board takes minutes. With 3,000+ events holding open markets, 3,000 requests at 5 a second take 600 s: about 10 minutes for one look at each event (the burst of 120 saves under half a minute). Soccer pre-match alone held 1,323 events when we counted on November 6, 2026: 4.4 minutes per pass.
Polling also loses what happens between two looks. If a line moves and moves back within your interval, you
never see it, and the version you read jumps over every change in between. For a dashboard that
is fine; for a bot that trades on moves, those are the moves.
4. When REST polling is enough
-
Pre-match, far from the start. Pre-match prices move far less often than in-play ones. A
model that reprices a slate once an hour reads it once an hour. Ask with
?channel=pre(see Pinnacle pre-match odds). - A few events. Five matches every 10 s are 30 requests a minute, a tenth of the limit.
- Dashboards and research. A page that refreshes when someone looks at it, or a notebook comparing Pinnacle with Polymarket and Kalshi.
-
Margin-free prices and closing lines on demand.
?fair=trueadds the no-vig and fair prices of every market (reference), and/v1/closingreturns the closing line of a match once it has started (reference). Both are on every plan; the maths is in Pinnacle no-vig odds.
5. When you need the Pinnacle odds stream
- In-play. Live prices move with every attack, break of serve and timeout. Seconds of delay are the difference between a price that is still there and one that has gone.
- Many events. One connection carries every sport you subscribe to; by REST, the same board is 10 minutes per pass.
-
Value and CLV bots. A value bet against
Pinnacle's fair price exists only until the other book or market catches up. With
{"type": "subscribe", "fair": true}eachoddsis followed by itsfairmessage, and{"type": "subscribe", "closing": true}sends the closing line of each match as it starts (fair, closing, CLV calculator). - Prediction-market trading. Quoting on Polymarket or Kalshi against Pinnacle means repricing the moment Pinnacle moves.
Nothing here is betting or investment advice: a faster price is a tool, not an edge on its own.
6. How a stream client keeps a correct book
A stream gives you changes, so you keep the state yourself. The rules are short, and none of them raises an error when broken: you just end up with wrong prices.
-
Start from a snapshot. Your
subscribebrings a snapshot of what it adds, in parts of up to 100 events; the last one has"final": true. -
Apply updates by version. Key events by
rec.idand markets bykey, and take a market only when itsversionis higher than the one you hold. A market whosestatusis not"open"leaves the book, andop: "del"removes the whole event, on whichever channel it comes. -
On
missed, orfeed"up", ask for a snapshot. Your queue holds 1,000 updates; past that the oldest are dropped and you are told how many. After a snapshot of everything you follow, drop what it did not mention. - Reconnect with backoff. Exponential with jitter, 1 to 30 s, reset after a minute connected; stop on 4101, 4103, 4107 and 4109, which retrying cannot fix (reconnecting). Treat 40 s with no message at all as a dead connection.
-
Keep the read loop light. A client more than 15 s behind is closed with
too_slow(4108). Read in one task, trade in another.
The full recipe is in Keeping a correct book, and both tutorials build it end to end: Pinnacle odds in Python and Pinnacle odds in Node.js.
7. The minimal code: a WebSocket subscribe and a REST poll loop
The stream, in Node.js 18+ with npm install ws: log in, subscribe to soccer on the
ld and both channels (the price Pinnacle displays), and print every message. The
snapshot arrives first, then each change.
import WebSocket from 'ws';
const ws = new WebSocket('wss://feed.oddspush.com/v1/stream', { perMessageDeflate: false });
ws.on('open', () => {
ws.send(JSON.stringify({ type: 'login', token: process.env.ODDSPUSH_KEY }));
ws.send(JSON.stringify({ type: 'subscribe', sports: [1], channels: ['ld', 'both'] }));
});
ws.on('message', (raw) => {
const msg = JSON.parse(raw.toString());
if (msg.type === 'odds') console.log(msg.data.topic, msg.data.rec?.id, msg.data.rec?.markets?.length ?? 0);
else console.log(msg.type); // welcome, subscription, snapshot, keepalive...
});
ws.on('close', (code) => console.log('closed', code));
The poll, in Python with the standard library only: refresh two events every 10 s, print the markets whose
version changed, and back off on 429.
import json
import os
import time
import urllib.error
import urllib.request
BASE = "https://feed.oddspush.com/v1"
KEY = os.environ["ODDSPUSH_KEY"]
WATCH = [1638780974, 1638781120] # the event ids you follow
EVERY_S = 10 # 2 events every 10 s = 12 requests a minute
def get(path: str) -> dict:
req = urllib.request.Request(BASE + path, headers={"x-token": KEY})
with urllib.request.urlopen(req, timeout=10) as r:
return json.loads(r.read())
seen = {} # (event id, market key) -> version last printed
while True:
for event_id in WATCH:
try:
ev = get(f"/events/{event_id}")
except urllib.error.HTTPError as e:
if e.code == 429: # over the rate limit: wait what the server says
time.sleep(int(e.headers.get("retry-after", "1")))
elif e.code != 404: # 404: the event is gone (settled or removed)
raise
continue
for key, m in ev["merged"].items(): # ld and both, merged by version
if seen.get((event_id, key)) != m["version"]:
seen[(event_id, key)] = m["version"]
print(event_id, key, [(x["designation"], x["price"]) for x in m["prices"]])
time.sleep(EVERY_S)
8. Plans and prices: REST or REST + WebSocket
Both plans carry the same prices at the same moment, live and pre-match, all 13 sports, with no-vig, fair and closing odds. The difference is the stream.
- REST: $69 a month ($186 for 3 months, $331 for 6). Pull the book whenever you need it, within the rate limit.
- REST + WebSocket: $138 a month ($373 for 3 months, $662 for 6). Everything in REST, plus one WebSocket connection with every sport on it and 12 snapshots an hour.
The free trial gives you both for 7 days, so you can measure the difference from your own server before you choose (pricing). Paid plans are prepaid in crypto.
Measure it from your own bot
The free trial gives you REST and the live stream, all 13 sports, for 7 days. No card.
OddsPush is not affiliated with, endorsed by or sponsored by Pinnacle. Nothing on this page is betting or investment advice.