Once an odds feed is live, three checks keep you honest: availability, freshness against the timestamp, and coverage of your books and markets. Here is how to build them so you catch a problem before your users do.
James··7 min read
Monitor an odds feed on three things: is it responding (availability), is the data fresh (staleness against the timestamp), and is coverage complete (are your books and markets present). Alert when any of the three drifts, and you catch a problem before your users do. Everything else is refinement. This post shows how to implement each check, and how to alert on them without drowning in noise.
The context that makes this matter: a stale price looks exactly like a live one. Your users cannot tell that the bet365 back price they are acting on was last seen twenty minutes ago. A feed that is up but frozen is the failure mode that silently breaks trust, so monitoring is not optional infrastructure. It is how you turn the feed's reported state into confidence you can stand behind.
What should I actually monitor?
Three checks cover the failure modes that matter, and they map cleanly to three questions a user would ask if they could see behind the curtain:
Availability: is the endpoint responding at all, with a 200 and valid JSON? This is the coarse check. If it fails, nothing downstream works.
Freshness: is the newest row recent, or is the feed up but frozen? This is the check people forget, and the one that hurts most, because a frozen feed passes the availability check.
Coverage: are your expected books and markets present, in roughly the expected quantity? An upstream gap can drop bet365 from a response while every other book stays whole, and availability and freshness both stay green.
These are independent. A feed can be available and fresh but missing a book. It can be available and complete but ten minutes stale. You want a signal for each, because a single "is it working" check will pass right through two of the three real failures.
How do I check availability?
Run a canary poll: a small, scheduled request against the same endpoint your app uses, on a fixed cadence, that asserts the response is well-formed. Point it at a narrow slice, for example one region and one market, so the check is cheap and fast. It should verify three things: the HTTP status is 200, the body parses as JSON, and the top-level envelope has the fields you expect.
Do not reinvent the shape of a good response to assert against; match the documented one. The data array and the metadata that wraps it are described in the response envelope, and asserting against those field names keeps your canary honest as the feed evolves. Keep the canary's cadence sensible: it is a health check, not a data pull, so it need not run tighter than your real polling. For that side of the trade-off, see polling efficiently.
How do I detect stale odds?
Compare a row's timestamp against now, and alert when the gap exceeds a threshold. This is the freshness check, and it is the single most valuable thing you can monitor, because staleness is invisible to your users until they act on a dead price. A feed that publishes a per-row or per-response freshness field makes this trivial: you read the timestamp, subtract from the current time, and compare against a budget you choose.
Set the threshold against the feed's stated cadence, not an arbitrary number. Our posture is pre-match polling on roughly a few-second cycle, so a row that is minutes old is a real signal, not jitter. Pick a budget with headroom, for example alert if the newest row is older than a few multiples of the cycle, so a single slow poll does not page anyone. Here is the shape of the comparison against a single row (illustrative, not live data):
Freshness check · illustrative
// one matched row from the feed
{
"event": "Arsenal vs Chelsea",
"market": "match_odds",
"selection": "Arsenal",
"back": { "bookmaker": "bet365", "odds": 2.10 },
"lay": { "exchange": "betfair", "odds": 2.14, "liquidity": 1840 },
"rating": 98.1,
"qualifying_loss": -0.12,
"seen_at": "2026-07-01T09:41:58Z" // freshness field
}
// the check: is the newest row within budget?
const ageSeconds = (Date.now() - Date.parse(row.seen_at)) / 1000;
if (ageSeconds > STALE_BUDGET_SECONDS) alertStale(row);
One subtlety: a market that is legitimately suspended will stop updating, and that is correct behaviour, not staleness. Your freshness check should read the market's state rather than pure timestamp age, so a suspended selection does not fire a false stale alert. The distinction between a frozen feed and a correctly suspended and void markets is exactly the kind of thing that separates a noisy monitor from a trustworthy one.
How do I check coverage is complete?
Count what you receive and compare it against what you expect. Coverage failures are partial by nature: the feed responds, the rows that arrive are fresh, but a book or a market quietly dropped out of a response. The check is a count. For a given region and market, how many distinct bookmakers came back, and is bet365 among them? How many selections did this event carry, versus the number you saw an hour ago?
Two counts are worth asserting on. First, an expected-books count: the feed carries 60+ UK books with bet365 included, so a poll that returns a handful is a coverage incident even if every row in it is valid. Second, an expected-selections count per market: a match_odds market that suddenly has one selection instead of the full set has lost its lay pairing or dropped a runner. Alert on a meaningful drop from a rolling baseline rather than an exact number, because real markets shift as events open and close.
How do I alert without drowning in noise?
Alert on sustained drift, not on single readings. A monitor that pages on every one-off blip trains you to ignore it, which is worse than no monitor. Three habits keep the signal clean:
Require persistence. Fire only after a check fails for N consecutive polls or a rolling window, so a single slow response or a momentary reparse does not page anyone.
Tier by severity. A brief freshness wobble is a warning; a book missing for ten minutes or a dead endpoint is a page. Route them to different channels so the urgent ones stay urgent.
Baseline, then compare. Coverage counts drift naturally as events open and settle, so alert on deviation from a rolling baseline rather than a fixed threshold that you will spend your week tuning.
The freshness check in particular is only as easy as the feed makes it. When the feed publishes a timestamp on each row or response, staleness detection is one subtraction. When it does not, you are forced to infer freshness from whether values changed, which is unreliable, because a genuinely unchanged price is indistinguishable from a frozen one. A feed that reports its own state is doing half your monitoring for you.
Where the responsibility actually sits
The feed reports its state; monitoring is how you turn that report into confidence. A well-built feed gives you the raw materials: a predictable envelope to assert against, a freshness timestamp to measure age from, and consistent coverage to count. Your monitoring reads those materials and decides when to act. Neither side does the whole job alone, and it is worth being honest about that split rather than pretending a feed can promise you will never need to watch it.
OddsRelay is built to make all three checks straightforward: one predictable endpoint for the availability canary, a freshness field so staleness is a subtraction, and 60+ UK books with bet365 included, matched against three exchanges (Betfair, Smarkets, Matchbook), so your coverage counts have a stable baseline. It powers a leading UK matched-betting platform today. The field list you would assert against is in the API docs, and the fastest way to point a canary at the real thing is a free trial: wire up the three checks against a live feed and watch them stay green before you commit.
James is the founder of OddsRelay — the odds-data feed behind matched betting, arbitrage and odds-comparison products: 60+ UK bookmakers with bet365 included, matched against exchange lay prices and delivered as one clean, documented API. He writes here about how that data layer actually behaves — coverage, matching, freshness and the trade-offs — from the side that builds and runs it. The same feed powers a leading UK matched-betting platform today.