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.

APILine-movement pushResolution pushDelivery model
PropLineYesYesHMAC-signed webhook, retried, filterable
the-odds-apiNoNo (no resolution at all)Pull only — you poll
OddsJam APINoNoPull only — you poll
SportsGameOddsNoNoPull 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.

EventFires whenKey payload fields
line_movementA 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
resolutionA 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
testYou 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.

FilterEffect
filter_sport_keyOnly events for one sport (e.g. baseball_mlb).
filter_event_idOnly one specific game.
filter_market_keyOnly one market (e.g. batter_total_bases).
filter_player_nameCase-insensitive substring on the player (e.g. “judge”).
min_price_change_pctline_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-Delivery

Node / 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. 1. Exponential-backoff retries. A non-2xx (or a timeout) is retried on a 10s → 30s → 2m → 10m → 30m → 1h schedule, up to 6 attempts. Transient downtime on your side doesn't drop the event.
  2. 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. 3. A delivery log you can audit. GET /v1/webhooks/{id}/deliveries returns 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.