Pinnacle WebSocket API: Protocol, State, When to Use It

How to consume a Pinnacle WebSocket API feed: raw passthrough protocol, the state you must hold yourself, and when REST or SSE is the better call.
Most teams who ask for a raw socket do not need one. They need a push channel for a narrow event — a price moving against itself — and that is what Server-Sent Events are for. The raw socket is for the minority who want every message Pinnacle emits, in order, and are willing to own the state machine that turns those messages into a coherent book.
pinnodds gives you both. /ws/feed is a raw WebSocket passthrough of the Pinnacle feed. /odds-drop and /odds-drop-prematch are SSE channels that push only drop alerts. And there is a REST surface for everything you can express as a question rather than a subscription. This post is about picking correctly, because the wrong pick costs you weeks of reconciliation code.
Why the delivery mechanism matters more than the data
Pinnacle closed its public API in July 2025. What replaced it, in most stacks, was polling: hit an endpoint on a timer, diff the payload, hope your interval was tight enough to catch the move. Polling fails in a specific and expensive way — it does not just delay you, it hides transitions. A line that goes 2.10 → 1.95 → 2.05 inside your poll window looks like it never moved.
A pushed feed removes the window. When Pinnacle changes a price, the change comes to you. That is the entire argument for the pinnacle websocket api shape of integration, and it is why the drop detector on top of it can be honest about from_price and to_price — those are two real observed states, not two samples.
pinnodds pushes sub-second. What it does not do is decide for you whether you want the firehose or the filter.
What comes over /ws/feed
/ws/feed is a passthrough. Authenticate with ?key=YOUR_KEY on the URL (the REST endpoints use the x-api-key header instead), and you receive the upstream message stream as-is.
That word — passthrough — is doing a lot of work. It means:
- No normalisation layer. The message shapes are the upstream shapes. If Pinnacle emits an incremental update for one selection on one line, that is what lands in your handler.
- No enrichment. There is no
drop_pct, no precomputed deltas. Those are things pinnodds computes for the REST and SSE paths, not something the raw socket adds. - Full line depth. Every line Pinnacle prices comes through, quarter lines included. Soccer totals at 1.75 / 2.25 / 2.75 / 3.25, quarter-ball handicaps at -0.75 / -1.25 / -1.75 / -2.25. Depth is identical on every plan — plans differ on rate limit and push access, not on what you can see.
If you want a structured, request-shaped view of the same markets, that lives in REST: GET /kit/v1/markets with event_type for live or prematch, /kit/v1/details for a single event, and the prematch trio /kit/v1/prematch/fixtures, /kit/v1/prematch/markets, /kit/v1/prematch/lines. Full reference is in the docs.
The state management burden, stated plainly
This is the part people underestimate. A socket does not hand you a book. It hands you a sequence of mutations, and you are the database.
Concretely, you own:
A snapshot to mutate. Incremental updates are meaningless against nothing. The standard pattern is to seed from REST — pull /kit/v1/markets or /kit/v1/prematch/lines — then apply socket messages on top of it. Get the ordering wrong and you apply a stale snapshot over fresh updates.
Reconnect and resync. Sockets drop. Networks reroute, processes get redeployed, upstream reconnects happen. Every disconnect creates a gap, and a gap means your in-memory book is now a guess. Reconnect logic that does not re-seed from REST is a bug that surfaces three hours later as a phantom edge.
Suspension and removal. Markets suspend mid-event and lines get pulled. Your state needs a representation for "this existed and no longer prices" that is distinct from "I have not seen this yet." Conflating them is how you end up betting into a market that closed.
Memory discipline. Turn on include_specials=1 and the shape of the problem changes. Soccer prematch is roughly 1,500 events without the flag and roughly 12,400 with it — player and team props, exact scores, futures and outrights arrive as their own event rows, each with special, special_category, special_markets and a parent_id pointing at the parent fixture. That is a legitimate and useful expansion, but holding all of it live is a different memory profile from holding main markets only.
None of this is exotic engineering. It is just work, and it is work you do not do if SSE covers your use case.
Where SSE wins, and it usually does
If your product is "tell me when a price falls against its own recent history," use /odds-drop or /odds-drop-prematch and stop reading. You get a pushed event per move, with no state to maintain, because each alert is self-contained.
The SSE payload names the market in sect, the moving selection in outcome, the prices in from_price and to_price, the event in id, and carries limit (Pinnacle's max stake on that selection) and nvp (the no-vig fair price). What it does not carry is a drop percentage — that is genuinely the difference between the two paths, and it trips people up.
The enriched form lives at REST. A row from /api/drops (with mode=live or mode=prematch) uses market, designation, from/to, event_id — and adds a precomputed drop_pct. So the honest pattern for most teams: SSE for the trigger, /api/drops for the ranked view, and no socket at all.
import json
from pinnodds import Client
client = Client(api_key="YOUR_KEY")
# Pushed trigger: self-contained, no state to hold
for alert in client.odds_drop():
print(alert["sect"], alert["outcome"],
alert["from_price"], "->", alert["to_price"],
"limit", alert["limit"], "nvp", alert["nvp"])
# Enriched, ranked view when you want drop_pct
for row in client.drops(mode="live"):
if row["drop_pct"] >= 4:
print(row["event_id"], row["market"], row["designation"], row["drop_pct"])
Both official SDKs — npm install pinnodds and pip install pinnodds — are zero or low dependency and wrap the REST and SSE surface. If you are on the raw socket you are mostly writing your own client anyway.
When you actually need the raw socket
Four cases justify the state machine:
- You want every transition, not a category of transition. Drop alerts are a filter. If you are modelling the full path a line takes — rises, reversals, oscillation around a key number — you need the unfiltered stream.
- You are building your own signal. Steam, cross-market divergence,
limitchanges as a confidence proxy, quarter-line spreads: all of that needs the raw material. - You are recording history. There is no historical odds archive here — see the caveat below. If you want a time series, the socket is how you build it, starting the day you turn it on.
- You have many consumers behind one connection. Fan-out from a single socket into your own internal bus is a legitimate architecture, and it is cheaper than every service polling REST.
Outside those four, REST plus SSE is the correct engineering decision. There is more detail on the protocol on the Pinnacle WebSocket API page.
Where the socket is the wrong tool
Be honest with yourself before you commit:
- You need history. pinnodds has no historical odds archive.
/api/dropsis a recent view, not a warehouse. If your model needs two seasons of closing lines, the socket does not solve that — start recording, or buy a historical dataset elsewhere. - You need many books. This is Pinnacle only. Pinnacle is the sharpest single source and that is the point, but if your product is "compare 40 bookmakers," you want an aggregator, and you can layer pinnodds alongside it as the reference price.
- Your consumer is a UI on a timer. A dashboard refreshing every 30 seconds gains nothing from a socket and inherits a reconnect bug surface. Call
/kit/v1/markets. - You cannot hold state. Serverless functions, cron jobs, anything without a warm long-lived process — the socket is a bad match. Use REST.
- You are only after drops. Then the socket is strictly more work for the same outcome. SSE is on the Pro + SSE plan at $149/mo and Scale at $229/mo; Pro at $99/mo covers REST. Details at pricing.
- You want a stable contract. A passthrough follows upstream. The REST endpoints give you a shape pinnodds owns; the raw feed gives you a shape it forwards.
A sane integration order
Do it in this sequence and you will not repaint:
- Get a free trial key — seconds, no card — and hit
/healthand/ping. - Build against REST first.
/kit/v1/markets,/kit/v1/details,/api/drops. Learn the market and line vocabulary with a debugger, not a stream. - Add SSE. See whether
sect/outcome/from_price/to_price/limit/nvpalready answers your product question. For a lot of teams it does. - Only then open
/ws/feed— and write the re-seed path before you write the message handler.
Takeaway
Treat /ws/feed as a mutation log, not a data source. The socket's value is completeness; its cost is that you become responsible for the snapshot, the resync-on-reconnect, and the distinction between "suspended" and "unseen." If your requirement can be phrased as a question, use REST. If it can be phrased as "notify me when a price falls," use SSE and keep your process stateless. Reach for the raw feed when you need transitions nobody has filtered for you — and when you do, seed from /kit/v1/prematch/lines or /kit/v1/markets first, every single reconnect.
Frequently asked questions
Is there still a public Pinnacle API?
No. Pinnacle closed its own public API in July 2025. pinnodds is an independent service that carries the same market data over REST, WebSocket and SSE.
How do I authenticate to a Pinnacle WebSocket feed?
pinnodds uses a single API key. On the streams, including /ws/feed and the SSE endpoints, pass it as ?key=YOUR_KEY; on REST endpoints send the header x-api-key: YOUR_KEY. The same key is also your login.
Do I need WebSockets or is SSE enough for odds drops?
SSE is enough if you only care about prices falling against their own recent history. The /odds-drop and /odds-drop-prematch alerts are self-contained, so you hold no state. Use the raw socket only when you need every transition, not a filtered category of them.
Why does the SSE odds-drop alert have no drop percentage?
The SSE payload is the raw alert: sect, outcome, from_price, to_price, id, plus limit and nvp. The enriched form is the REST row at /api/drops, which uses market, designation, from/to, event_id and adds a precomputed drop_pct.
Does the WebSocket feed include quarter lines and player props?
Quarter lines come through on every plan — soccer totals at 1.75 / 2.25 / 2.75 / 3.25 and quarter-ball handicaps at -0.75 / -1.25 / -1.75 / -2.25. Props, exact scores, futures and outrights require include_specials=1, which expands soccer prematch from roughly 1,500 events to roughly 12,400.
Can I get historical Pinnacle odds from the API?
No. There is no historical odds archive. /api/drops is a recent view only, so if you need a time series you have to record the feed yourself from the day you connect.
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