Sportsbook Webhook API
Stop polling for odds.
Get pushed the moment they move.
The-odds-api, OddsJam and SportsGameOdds are pull-only — you run a poll loop and eat the latency between cycles. PropLine pushes a signed POST to your endpoint within seconds of a price moving or a prop grading. HMAC-SHA256 signed, exponential-backoff retried, filterable down to a single player.
Webhooks start on Streaming Lite — $39/mo, 100,000 req/day, 5 active webhooks; Streaming ($79/mo) raises that to 1,000,000 req/day and 10 webhooks. Free tier lets you wire the endpoint and verify signatures before upgrading.
Push beats pull
A pull-only odds API forces a trade-off: poll fast and burn your request quota on cycles where nothing changed, or poll slow and miss the move. A webhook removes the trade-off entirely — you get exactly one delivery per real change, the instant it happens, and your quota isn't touched by the wait.
This matters most for the two things that are time-sensitive: steam moves (a price moving sharply across books) and resolution(knowing a prop hit the second it's graded, not on your next poll). Both are push-native here.
| API | Line-movement push | Resolution push | Delivery model |
|---|---|---|---|
| PropLine | Yes | Yes | HMAC-signed webhook, retried, filterable |
| the-odds-api | No | No (no resolution at all) | Pull only — you poll |
| OddsJam API | No | No | Pull only — you poll |
| SportsGameOdds | No | No | Pull only — you poll |
What lands on your endpoint
A plain JSON POST with four headers. The signature is hex(HMAC-SHA256(secret, "{timestamp}." + body)) — timestamp-prefixed so a captured body can't be replayed. X-PropLine-Delivery is a stable id you dedupe on (retries reuse it).
POST https://your-endpoint.example.com/propline
Content-Type: application/json
X-PropLine-Event: line_movement
X-PropLine-Timestamp: 1747000000
X-PropLine-Signature: 4a7f...e91c
X-PropLine-Delivery: 9f1c2d3e-... // dedupe key
{
"event_type": "line_movement",
"sport_key": "baseball_mlb",
"event": {
"id": 11049,
"external_id": "bv-11049",
"home_team": "St. Louis Cardinals",
"away_team": "Seattle Mariners",
"commence_time": "2026-05-12T23:45:00+00:00"
},
"bookmaker_key": "draftkings",
"bookmaker_title": "DraftKings",
"market_id": 998877,
"market_key": "batter_total_bases",
"outcome_id": 776655,
"player_name": "Julio Rodriguez",
"outcome_name": "Over",
"previous": { "price_american": -110, "point": 1.5 },
"current": { "price_american": -125, "point": 1.5 },
"price_change_pct": 13.6,
"timestamp": "2026-05-12T18:42:07+00:00"
}Event types
Subscribe to either or both. The X-PropLine-Event header tells you which without parsing the body.
| Event | Fires when | Key payload fields |
|---|---|---|
| line_movement | A recorded outcome's American price or point changes on any book (first-insert is suppressed — you only get real moves, not the opening line). | sport, event, book, market, player, old → new price, old → new point, pct change |
| resolution | A prop grades against the official box score (won / lost / push / void) — pushed within seconds of the resolver settling the game. | sport, event, book, market, player, line, side, resolution, actual_value |
| test | You hit POST /v1/webhooks/{id}/test — a synthetic payload so you can verify your endpoint + signature check before live traffic. | Same envelope, dummy body. |
Filter at the source
Every filter is optional and AND-ed together. Set them on the subscription so you only receive the deliveries you care about — no client-side discard, no wasted bandwidth.
| Filter | Effect |
|---|---|
| filter_sport_key | Only events for one sport (e.g. baseball_mlb). |
| filter_event_id | Only one specific game. |
| filter_market_key | Only one market (e.g. batter_total_bases). |
| filter_player_name | Case-insensitive substring on the player (e.g. “judge”). |
| min_price_change_pct | line_movement only — suppress sub-threshold noise. Point-only shifts always pass regardless. |
Verify the signature in 1 call
Both official SDKs ship a static signature verifier so you never hand-roll HMAC. Verify against the raw request body bytes— before any JSON parse re-serializes and breaks the digest.
Python (official SDK)
# pip install propline
from propline import PropLine
# raw request body bytes + headers from your web framework
sig = request.headers["X-PropLine-Signature"]
ts = request.headers["X-PropLine-Timestamp"]
if not PropLine.verify_signature(
secret="whsec_...", # shown ONCE at webhook creation
timestamp=ts,
body=raw_body, # bytes, pre-JSON-parse
signature=sig,
):
return Response(status=401)
# safe to trust + dedupe on X-PropLine-DeliveryNode / TypeScript
// npm install propline
import { PropLine } from "propline";
const ok = PropLine.verifySignature({
secret: process.env.PROPLINE_WEBHOOK_SECRET!,
timestamp: req.headers["x-propline-timestamp"] as string,
body: rawBody, // Buffer, before JSON.parse
signature: req.headers["x-propline-signature"] as string,
});
if (!ok) return res.status(401).end();Built so you don't miss one
A push model is only useful if delivery is durable. Three guarantees:
- 1. Exponential-backoff retries. A non-2xx (or a timeout) is retried on a
10s → 30s → 2m → 10m → 30m → 1hschedule, up to 6 attempts. Transient downtime on your side doesn't drop the event. - 2. Stable delivery id for dedupe. Every retry of the same logical event carries the same
X-PropLine-Delivery. Key your idempotency on it and a double-delivery is a no-op. - 3. A delivery log you can audit.
GET /v1/webhooks/{id}/deliveriesreturns the last 50 attempts with response code, attempt count, and next-retry time — so a silent endpoint failure is visible, not mysterious.
Prefer Discord over a custom endpoint? Set format="discord"on the subscription and deliveries arrive as color-tinted embeds — three-step setup here.
Subscribe in 3 calls
Same auth as every PropLine endpoint. The signing secret is returned exactly once at creation — store it then; it's masked on every subsequent read.
# 1. Subscribe (returns the signing secret ONCE — store it now)
curl -X POST "https://api.prop-line.com/v1/webhooks?apiKey=YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://your-endpoint.example.com/propline",
"events": ["line_movement", "resolution"],
"filter_sport_key": "baseball_mlb",
"min_price_change_pct": 5
}'
# 2. Fire a test payload at your endpoint
curl -X POST "https://api.prop-line.com/v1/webhooks/{id}/test?apiKey=YOUR_KEY"
# 3. Inspect the last 50 delivery attempts (status, response code, retries)
curl "https://api.prop-line.com/v1/webhooks/{id}/deliveries?apiKey=YOUR_KEY"Full CRUD — GET / PATCH / DELETE /v1/webhooks/{id} to list, re-filter, or remove a subscription. Up to 5 active webhooks on Streaming Lite, 10 on Streaming.
What people build with it
The push model collapses three classes of tool from “polling pipeline + warehouse” to “one endpoint handler.”
Steam detector
Sharp-move alerts
Subscribe to line_movement with min_price_change_pctset. Every delivery is already a move worth looking at — no diffing two poll snapshots to find it.
Bet tracker
Instant settle notifications
Subscribe to resolutionfiltered to your users' players. Push “your prop hit” the second it grades — the engagement moment nobody else can deliver because nobody else grades props.
Discord / Slack bot
Community feeds
Point the webhook at a Discord channel with the built-in format="discord" adapter — zero code, color-tinted embeds by resolution.
Wire the endpoint free, go live on Streaming
Grab a free key, stand up your handler, and fire test payloads at it to confirm signature verification end-to-end. Upgrade to Streaming ($79/mo, 1,000,000 req/day) when you're ready for live line-movement and resolution traffic.
Free tier includes 1,000 requests/day. Upgrade anytime.
Coming from a pull-only API? 50% off Pro for 3 months in exchange for an invoice screenshot.