Reddit Monitoring Over Webhooks: The Delivery Contract, Measured
Reddit has no push. A hosted monitor is a poller somebody else runs. Here is what it guarantees, HMAC signing, the retry ladder, and how to prove it works.

A hosted Reddit monitor is a service that watches Reddit for your keyword and pushes each match to a URL you control. Four of them rank on the first page for this query and every one of them advertises webhook delivery. None of them tells you what happens to a match when your server returns a 500, how the payload is signed, how long a delivery is retried before it is abandoned, or how many posts the poller failed to fetch while it was running. Those four answers are the entire difference between a monitoring integration you can operate and one you find out about from a customer.
This post documents the delivery contract of a hosted Reddit monitor end to end, then shows the verification pass that proves it is working. Every number here is either read from the deployed source or measured on a live account on 2026-08-31, and each one says which it is.
Not affiliated with Reddit Inc. redditapis.com is an independent third-party REST proxy for Reddit's API. This guide is vendor-neutral about the category: it names the competing tools honestly, states plainly what Reddit itself does and does not provide, and points at the parts of our own product that are rounded, capped or missing.
TL;DR
Reddit has no push. Every Reddit webhook product on the market is a poller somebody else runs, and the honest question is not whether it pushes but what it guarantees when it does. On this API the contract is specific: an HMAC-SHA256 signature over ${timestamp}.${rawBody} in an x-redditapis-signature header formatted t=...,v1=..., a 300 second replay window, a stable x-redditapis-delivery-id for deduplication, and a retry ladder of 5s, 30s, 2m, 5m and 13m, six attempts over about 21 minutes, before a delivery is marked dead and left readable rather than dropped. Management calls and deliveries are plan billed, not call billed, so auditing your own monitors costs nothing. Measured across three real deliveries on one 60 second monitor on 2026-08-31, end to end time from the Reddit post existing to the alert being accepted was 61.7s, 71.2s and 87.3s, of which the signed POST itself took 2.0 to 4.2 seconds. The number nobody else publishes is what the poller missed: the health endpoint reported coverage_24h status degraded with 148 gap events, at least 90,655 posts saved and an estimated 39,676 missed, and it volunteered that the estimate leans high.
Does Reddit send webhooks for new posts?
No. Reddit has no push channel of any kind for an external server. There is no endpoint on the Reddit Data API where you register a callback URL and get notified when a post appears, which means every product that advertises a Reddit webhook is running a poller on your behalf and pushing from its own infrastructure. The push is real; Reddit is not the thing pushing.
This matters more than it sounds, because it relocates every hard question. If Reddit pushed, the interesting questions would be about Reddit's reliability. Since it does not, the interesting questions are all about the intermediary:
- How often does it poll, and is that the interval you are paying for?
- What does it do when its poll cannot keep up with the volume?
- What does it do when your endpoint is unreachable?
- Can it tell you what it missed?
The Reddit API documentation carries no callback registration surface, and the Reddit Data API Wiki describes a request budget rather than an event stream. The one place Reddit does offer event-shaped triggers is inside its own Developer Platform, and those fire for apps running on Reddit rather than for a server you own. The same is true from the client side: PRAW's stream helpers are documented as polling loops wrapped in a generator, not as an event subscription. We wrote up the underlying mechanics separately in webhooks versus polling for Reddit data streams, and the short version has not changed: there is no push, so the honest question is whose poller you want to run.
A founder surveying this category in r/SaaS framed the problem exactly right, and the framing is worth keeping:
What Reddit monitoring tools are you actually using in 2025? (founder doing research)
"Doing research for a few micro SaaS project I'm building, so genuinely curious about real experiences vs. marketing claims"
Real experience versus marketing claim is precisely what an undocumented delivery contract hides. A monitor looks identical in a demo whether it retries six times or zero times, because in a demo the endpoint is up. The same poster opened with the reason any of this matters: "realized I'm probably missing tons of Reddit mentions manually checking 20+ subreddits."
What does a hosted Reddit monitor actually do between the post and your endpoint?
A hosted monitor is a chain of six steps, and naming them separately is what makes the failure modes legible, because each step fails differently and only one of them is the webhook. A post exists on Reddit, a poller fetches a listing page, a matcher tests your keyword against the fetched item, a delivery record is written for each target you have registered, a signed POST goes to your URL, and your endpoint answers.
Step by step, with the part that can go wrong:
- The post exists. Nothing fires. Reddit's listing now contains one more item than it did.
- A poller fetches. It requests the newest page of the listing being watched and pages backward until it reaches the item the previous poll finished on. If the listing moved faster than the poller could page, this step loses data permanently. This is the step nobody documents.
- The matcher runs. Your keyword is tested against the fields named in
search_in, defaulting to title, body and url. This is a text match, not a semantic search. - A delivery record is written, one per registered webhook target. Two webhooks on one account means one Reddit post produces two delivery rows, each with its own identifier and its own outcome.
- A signed POST is sent to your URL, carrying an HMAC signature, a timestamp and a stable delivery identifier.
- Your endpoint answers. A 2xx marks the row delivered. Anything else routes into the retry ladder or straight to a terminal state, depending on the code.
Only steps 5 and 6 are the webhook. Steps 2 and 3 decide whether the webhook ever has anything to carry, and they are where the interesting failures live. A monitor that delivers every match perfectly while its poller silently drops a third of the listing is a monitor with a flawless delivery record and a useless output.
The control surface for all of this is ten endpoints, and they split cleanly into three jobs:
Configure is add, update, remove and list. Deliver is webhook/create, webhook/list, webhook/test and webhook/delete. Audit is health and deliveries. All ten are billed against your monthly plan rather than per call, which is worth stating plainly because it changes how you should use them: reading your own configuration, polling your own health and paging your own delivery history draws no API credits, so there is no cost argument against instrumenting them properly. The read endpoints allow 600 requests a minute per account, mutations allow 30, and test deliveries allow 10, which is generous enough that a health check every minute is unremarkable.
How fast does an alert actually arrive?
End to end latency for a hosted Reddit monitor is dominated by the poll interval, not by the webhook. Measured across three real deliveries on one sitewide keyword monitor running at a 60 second requested cadence on 2026-08-31, the time from the Reddit post existing to our endpoint accepting the alert was 61.7, 71.2 and 87.3 seconds. The signed POST accounted for 2.0 to 4.2 seconds of that measured total.
Here is the raw data rather than a summary of it:
Three real deliveries, post created to alert accepted
| Reddit post id | Post created (UTC) | Delivery row written | Accepted by endpoint | End to end | Source |
|---|---|---|---|---|---|
| 1w36n1m | 07:30:46 | 07:31:45.714 | 07:31:47.712 | 61.7s | measured |
| 1w3ikrk | 16:19:10 | 16:20:17.020 | 16:20:21.201 | 71.2s | measured |
| 1w2yh20 | 00:41:30 | 00:42:55.093 | 00:42:57.285 | 87.3s | measured |
Three deliveries is a small sample and it is presented as a worked example rather than as a service level. What the sample does establish is where the time goes, and the split is stable across all three rows: the detection leg ran 59.7 to 85.1 seconds and the delivery leg ran 2.0 to 4.2 seconds. Detection is roughly twenty times the delivery cost. Any effort spent optimising your receiving endpoint is therefore effort spent on the small number.
There is a second finding in that data, and it is the more useful one. The published figure for this product is detection under 40 seconds from the Growth tier upward, and the account these deliveries came from is on the Growth tier. The measured median was 71.2 seconds. The reason is not a broken promise, and the API itself said so when asked. The health endpoint returned a cadence object carrying promised_cadence_s: 30, requested_cadence_s: 60 and meets_entitlement: false, with this note attached:
"this monitor is polled slower than the floor your plan includes. A plan upgrade does not re-cadence monitors that already exist, so a monitor created on an older plan keeps its old interval until it is updated."
That is the whole explanation. The monitor was created when the account sat on a slower plan, the plan later improved, and the existing monitor kept its original interval because nothing rewrites a monitor you did not ask to have rewritten. The published 40 second figure describes a 30 second cadence monitor; this was a 60 second cadence monitor wearing a Growth badge. Setting cadence_s on it would close the gap.
Two things are worth taking from this. First, a marketing latency figure and your latency are different numbers, and the difference is usually a configuration you can read. Second, and more to the point of this post, an API that hands you meets_entitlement: false plus a sentence explaining it is doing something most monitoring products do not do at all, which is telling you when you are not getting what you bought.
Setting up a monitor and a webhook in four calls
Standing up a working Reddit monitor takes four HTTP calls: register a delivery target, send a test to prove the wiring, create the monitor, then read its health. Doing them in that order matters, because a monitor created before a webhook exists will match posts and have nowhere to put them.
First, register the webhook and capture the signing secret, which is shown exactly once:
curl -sS -X POST https://api.redditapis.com/api/reddit/monitor/webhook/create \
-H "Authorization: Bearer $REDDITAPIS_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://hooks.example.com/reddit"}'
The response carries the webhook row plus a secret field and secret_shown_once: true. Store the secret immediately in whatever your application uses for secrets; webhook/list will never return it again. The kind field is inferred from the host, so a hooks.slack.com or Discord webhook URL is detected automatically and the response reports what it inferred in kind_inferred_from. Passing a kind that contradicts the host is refused rather than stored, which is the right behaviour: a Slack URL sent a generic JSON envelope can never deliver, because Slack answers it with a 400.
Second, prove the wiring before you wait on a real match:
curl -sS -X POST https://api.redditapis.com/api/reddit/monitor/webhook/test \
-H "Authorization: Bearer $REDDITAPIS_KEY" \
-H "Content-Type: application/json" \
-d '{"id": "<webhook_id>"}'
Read the response body rather than the HTTP status here. A test whose delivery failed still returns 200, with delivered: false in the body alongside reason, status, detail and hint. The detail field carries a bounded copy of what your server actually said, and hint is a sentence naming the likely fix. A bare http_error with a 400 names no field and no remedy; the destination's own reply usually is the diagnosis.
Third, create the monitor:
curl -sS -X POST https://api.redditapis.com/api/reddit/monitor/add \
-H "Authorization: Bearer $REDDITAPIS_KEY" \
-H "Content-Type: application/json" \
-d '{
"filter_spec": {
"q": "clipping agency",
"search_in": ["title", "body"],
"exclude_terms": ["giveaway", "hiring"]
},
"cadence_s": 60
}'
Omitting subreddit and supplying q creates a sitewide monitor covering every subreddit at once. Supplying subreddit as an array of names scopes it to those communities. A monitor must be anchored by one or the other; neither is refused. Note that cadence_s may only be slower than your plan's floor, never faster: a value below the floor is silently raised to it rather than rejected, so read back what you got rather than assuming.
Fourth, read the health, which is the call most integrations skip and the one this whole post argues for:
curl -sS "https://api.redditapis.com/api/reddit/monitor/health?id=<monitor_id>" \
-H "Authorization: Bearer $REDDITAPIS_KEY"
Monitors are forward looking from the moment of creation. Nothing is backfilled, so a monitor created today will never tell you about a post from yesterday, and the correct way to cover history is a search call rather than a monitor. If you need matching to start from a specific known item rather than from now, pass baseline_item_id with a Reddit post fullname and matching begins strictly after it.
How do you verify a Reddit monitoring webhook signature?
Verifying a delivery means recomputing an HMAC-SHA256 over the exact string ${timestamp}.${rawBody} with your webhook's signing secret, then comparing it in constant time against the value the request carried. The signature travels in an x-redditapis-signature header formatted as two comma-separated parts, t=<unix seconds>,v1=<hex digest>, so the timestamp you sign with is inside the header itself rather than something you invent.
Three headers arrive on every delivery:
| Header | Carries | What you do with it |
|---|---|---|
x-redditapis-signature |
t=<unix seconds>,v1=<hex> |
Split it, recompute, compare in constant time |
x-redditapis-timestamp |
The same unix seconds value | Convenience copy, do not sign this one instead of t= |
x-redditapis-delivery-id |
A stable id for this delivery | Deduplicate on it, because delivery is at least once |
The single most common way to get this wrong is signing a re-serialised object instead of the raw bytes. Any framework that parses JSON before your handler runs has already destroyed the exact byte sequence that was signed, and re-serialising it will reorder keys or change whitespace and produce a mismatch that looks like a broken secret. Capture the raw body first.
In Node with Express:
const crypto = require("crypto");
const express = require("express");
const app = express();
// Raw body, not the parsed object. This line is the whole trick.
app.use("/reddit", express.raw({ type: "application/json" }));
function verify(secret, header, rawBody, toleranceSeconds = 300) {
if (!secret || typeof header !== "string") return false;
const parts = Object.fromEntries(
header.split(",").map((kv) => {
const i = kv.indexOf("=");
return i < 0 ? [kv.trim(), ""] : [kv.slice(0, i).trim(), kv.slice(i + 1).trim()];
})
);
const t = Number(parts.t);
const given = parts.v1;
if (!Number.isFinite(t) || !given) return false;
// Math.abs, so a clock-skewed FUTURE timestamp is rejected too. A future
// timestamp that only failed the "too old" test would stay valid forever.
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - t) > toleranceSeconds) return false;
const mac = crypto.createHmac("sha256", secret);
mac.update(`${t}.${rawBody.toString("utf8")}`);
const expected = Buffer.from(mac.digest("hex"), "utf8");
const actual = Buffer.from(given, "utf8");
// Length first: timingSafeEqual throws on a mismatch, which itself leaks length.
if (expected.length !== actual.length) return false;
return crypto.timingSafeEqual(expected, actual);
}
app.post("/reddit", (req, res) => {
const ok = verify(process.env.REDDIT_WEBHOOK_SECRET,
req.get("x-redditapis-signature"), req.body);
if (!ok) return res.status(401).send("bad signature");
const deliveryId = req.get("x-redditapis-delivery-id");
if (alreadySeen(deliveryId)) return res.status(200).send("duplicate");
const payload = JSON.parse(req.body.toString("utf8"));
enqueue(payload.items); // do the slow work off the request
res.status(200).send("ok"); // answer fast, 2xx is all that is checked
});
The same thing in Python with Flask:
import hmac, hashlib, time, json
from flask import Flask, request, abort
app = Flask(__name__)
TOLERANCE_S = 300
def verify(secret: str, header: str, raw: bytes) -> bool:
if not secret or not header:
return False
parts = dict(
(kv.split("=", 1) + [""])[:2] for kv in header.split(",")
)
try:
t = int(parts.get("t", "").strip())
except ValueError:
return False
given = parts.get("v1", "").strip()
if not given:
return False
# Reject stale AND future-dated, same as the Node version.
if abs(int(time.time()) - t) > TOLERANCE_S:
return False
expected = hmac.new(
secret.encode("utf-8"),
f"{t}.".encode("utf-8") + raw,
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, given)
@app.post("/reddit")
def reddit_hook():
raw = request.get_data() # bytes, before any JSON parsing
if not verify(SECRET, request.headers.get("x-redditapis-signature", ""), raw):
abort(401)
delivery_id = request.headers.get("x-redditapis-delivery-id")
if already_seen(delivery_id):
return "duplicate", 200
enqueue(json.loads(raw)["items"])
return "ok", 200
The conventions here are not invented locally. A two-part header carrying its own timestamp, a constant-time comparison and a bounded replay window are the pattern documented across the industry at webhooks.fyi, and the two primitives the snippets use are createHmac and timingSafeEqual from the Node standard library.
Four details worth being deliberate about. The tolerance window is 300 seconds and it is checked with an absolute value, so a request whose timestamp is five minutes in the future is rejected as firmly as one five minutes in the past; do the same on your side rather than only testing for staleness. The comparison is constant time, and the length check comes first because a timing-safe comparison typically throws on a length mismatch and that throw would itself leak the length. The delivery identifier is a header rather than a body field precisely so you can deduplicate before parsing. And answering fast matters: only the status code is examined, so acknowledge with a 2xx and do the real work asynchronously, because a slow handler turns a delivered alert into a retried one.
One asymmetry to know if you are sending to Slack or Discord. Those targets receive the same signature headers as a generic webhook, even though neither platform can verify them and neither is expecting to. There is nothing to do about it and nothing breaks, but if you are reading a delivery in a proxy and wondering why a Slack-shaped payload carries an HMAC, that is why.
Start building with Redditapis
Reads $0.002, votes $0.005, writes $0.012, DMs $0.025. $0.50 free credits.
What happens when your endpoint is down?
A failed delivery is retried on a fixed ladder of five delays, 5 seconds, 30 seconds, 2 minutes, 5 minutes and 13 minutes, which is six attempts including the first one. Those delays sum to 20 minutes and 35 seconds, and each is jittered by up to a fifth so that every account's retries do not all land on the same second after a shared incident. When the sixth attempt fails, the delivery row is marked dead and left in place with the error your server returned.
The retry ladder, attempt by attempt
| Attempt | Delay before it | Elapsed when it fires | Still retrying after this | Source |
|---|---|---|---|---|
| 1 | none, immediate | 0s | yes | published |
| 2 | 5s | 5s | yes | derived |
| 3 | 30s | 35s | yes | derived |
| 4 | 2m | 2m 35s | yes | derived |
| 5 | 5m | 7m 35s | yes | derived |
| 6 | 13m | 20m 35s | no, row becomes dead | derived |
The ladder is front loaded on purpose. A deploy that takes ninety seconds is fully covered by the first four attempts; an outage that lasts an hour is not covered at all, and no ladder that also behaves politely toward a stranger's server would be. That is the trade, and it is worth knowing which side of it your incident response sits on.
Three properties of this that a buyer should establish about any monitoring vendor, not just this one:
- What is the total window? Here it is a little under 21 minutes before jitter. Some products retry for 24 hours; some retry once; some do not say.
- Is there a replay path? Here, no. This is the sharp edge and it deserves a plain sentence rather than a footnote: there is no redrive, replay or manual re-send for a delivery that has gone dead. The row remains readable through the deliveries endpoint with its payload intact, so nothing is hidden from you, but nothing will re-attempt it either. If your endpoint was down for an hour, the correct recovery is to page the deliveries endpoint for that window, read the payloads out of the dead rows, and process them yourself. That is a real operational task and you should write it before you need it.
- What happens to the webhook itself? Nothing automatic. A failing target is not disabled or paused. Two counters accumulate on the webhook row,
consecutive_failuresandfailure_debt, and once they cross a threshold an email goes out, with a quiet period so a long outage does not turn into a mailbox flood.
That last one is observable, and here is a real dead target on a live account, sanitised only to remove the URL:
{
"id": "529978ac-...",
"kind": "webhook",
"url": "https://example.com/<redacted>",
"active": true,
"last_ok_at": null,
"last_error": "http_error(405): <!doctype html><html lang=\"en\"><head><title>Example Domain</title>...",
"consecutive_failures": 7,
"failure_notified_at": "2026-08-31T00:42:58.713+00:00",
"failure_debt": 12
}
Read that row for a second, because it is a small lesson in what good failure reporting looks like. The endpoint is example.com, which answers a POST with a 405 because it only serves GET. The API captured the status code and a bounded copy of the HTML body, so the diagnosis is right there in last_error rather than requiring a support ticket. last_ok_at is null, meaning this target has never once succeeded, which is a stronger signal than a failure count. And failure_notified_at proves an alert was actually sent rather than the failure being recorded silently.
The reason this example exists is worth stating plainly, because it is the honest version of a dogfooding story: it is a deliberately dead endpoint left in place on our own account to keep a failing path visible. It has produced a lot of dead deliveries and exactly the behaviour documented above.
Which HTTP responses get retried, and which are terminal?
Only a 2xx counts as success. Everything else divides into responses worth trying again and responses that will never succeed, and the division is deliberately narrow: retrying a request the receiver has already refused is not persistence, it is repeatedly hammering somebody's server under our name.
The full mapping:
| Your response | Retried | Ends as | Why |
|---|---|---|---|
| 200 to 299 | No | delivered |
Success is the whole 2xx range, nothing narrower |
| 300 to 399 | No, and never followed | dead |
A redirect is refused rather than followed, so a misconfigured URL is visible |
| 400, 404, 410, 422 | No | dead with attempts at 0 |
The payload was refused; repeating it changes nothing |
| 401 or 403 | Yes | dead after six |
An auth rejection says your secret and ours disagree right now, which ends by itself |
| 408 or 429 | Yes | dead after six |
The receiver is explicitly asking for later |
| 500 to 599 | Yes | dead after six |
The receiver is broken, not the payload |
| Network error or timeout | Yes | dead after six |
Nothing was refused because nothing was received |
Two of those rows are worth dwelling on.
Redirects are refused, not followed. If your webhook URL 301s to its real location, delivery fails and the row goes dead immediately. This looks unhelpful the first time it bites you and it is the correct behaviour: a followed redirect defeats the address validation that stops a webhook URL being pointed at a private network address, and a permanently redirecting endpoint is a configuration you want told about rather than silently worked around. Register the final URL.
A 401 is treated as transient, and that is not the obvious choice. The obvious reading is that any 4xx means the payload was refused, so do not retry. That reading is right for 400 and 404 and wrong for authentication, because a signature rejection says nothing about the payload; it says the receiver's idea of the shared secret and ours disagree at this moment. That disagreement is what a secret rotation or a redeploy looks like from the sending side, and it ends on its own within a minute or two. Classifying 401 as permanent turns a routine rotation into silent, unrecoverable alert loss for exactly as long as the rotation takes. Retrying it costs at most six polite attempts.
The practical consequence for your handler: return a 401 while you are mid-rotation and the deliveries will wait for you. Return a 400 because you could not parse something and they will not.
Per-attempt timeout is 10 seconds, so a handler that blocks on slow downstream work will burn the ladder without ever having failed for a reason worth retrying. Acknowledge first, work second.
Reading a delivery record: what was actually sent
The deliveries endpoint answers a different question from health. Health tells you how many; deliveries tells you what, with the actual Reddit content that was pushed, newest first. The deliveries endpoint takes an optional monitor id, an optional status filter, a limit from 1 to 200 defaulting to 50, and a before cursor which is the created_at of the oldest row on your previous page.
curl -sS "https://api.redditapis.com/api/reddit/monitor/deliveries?limit=50&status=dead" \
-H "Authorization: Bearer $REDDITAPIS_KEY"
Omitting id aggregates across every monitor you own, which is the query you want during an incident. A row looks like this, trimmed for length:
{
"delivery_id": "6c9cba38-e8a4-f9d6-2e29-a167438eb53a",
"monitor_id": "fa8388ee-...",
"webhook_id": "ba09f9f7-...",
"status": "delivered",
"attempts": 0,
"last_error": null,
"created_at": "2026-08-31T16:20:17.020327+00:00",
"delivered_at": "2026-08-31T16:20:21.201+00:00",
"payload": {
"count": 1,
"grouped": false,
"item_ids": ["t3_1w3ikrk"],
"items": [{
"id": "1w3ikrk",
"title": "[JOB OPENING] Auracle is hiring Clippers",
"subreddit": "RemoteJobs",
"permalink": "/r/RemoteJobs/comments/1w3ikrk/...",
"author": "Deepexee",
"created_utc": 1788193150,
"enrichment": {
"relevance": { "score": 0.85, "method": "keyword_match_heuristic",
"matched_terms": ["clipping agency"] },
"intent": { "tag": "recommendation_request", "method": "rule_based_heuristic" },
"sentiment": { "label": "positive", "method": "lexicon_heuristic" }
}
}]
}
}
Five things in that record repay a close look.
attempts is 0 on a successful first try. It counts retries, not tries. A dead row also showing 0 therefore means the response code was classified permanent and the ladder was skipped entirely, which is a different failure from a dead row showing 5.
The status enum declares five values and four are written. pending, delivered, dead and suppressed all occur. failed is declared and never written, so filtering on status=failed returns an empty list forever. If you are looking for failures, the terminal state you want is dead.
Every enrichment field names its own method. keyword_match_heuristic, rule_based_heuristic, lexicon_heuristic. These are deterministic rules, not model calls, and the payload says so rather than letting a sentiment: positive field imply more than it is. Treat relevance.score as a match-strength hint, not as a judgement about whether the post is a good lead. A competing vendor's founder put the real number on this in public, disclosing his affiliation as he did it:
Alternative to RSS for real time web monitoringand notifications?
A practitioner comparing tools in the r/SaaS survey thread above put the category honestly: "They range from simple keyword alert monitoring to more advanced intelligence or brand monitoring platforms." A keyword monitor is the simple end of that range by design, and its enrichment block is a sorting aid rather than a judgement. Whatever tool you use, plan for most matches to be noise and build the filter you actually need on your side of the webhook.
One match produces one row per target. A single Reddit post delivered to two registered webhooks yields two rows sharing a created_at and an item_ids array but carrying different delivery_id and webhook_id values, and independent outcomes. In the account examined, every match produced one delivered row to a working Slack target and one dead row to the deliberately broken endpoint. Aggregate counts across webhooks will therefore look inflated relative to the number of Reddit posts if you forget the fan-out.
Delivery history is not permanent. Rows are retained for 14 days by default. That is ample for reconciliation and far too short to be your archive, so if you need durable history, write the payloads to your own store when they arrive rather than planning to page them back later.
What did the monitor actually miss?
The most important number in monitoring is the one describing what the poller failed to fetch, and it is the number almost nobody publishes, because publishing it requires measuring it first. On this API it lives in coverage_24h on the health endpoint, and here is what one live sitewide monitor reported about itself on 2026-08-31:
What the health endpoint said about itself
COVERAGE STATUS
degraded
GAP EVENTS, 24H
148
EST. POSTS MISSED
39,676
The raw fields, unedited:
"coverage_24h": {
"status": "degraded",
"gap_events_24h": 148,
"posts_in_window_at_least": 90655,
"posts_missed_estimate": 39676,
"estimate_available": true,
"estimate_complete": false,
"estimate_leans": "high",
"no_estimate_reasons": ["rate_model_contradicts_the_observation"],
"observed_events": ["listing_exhausted", "poll_overflow"],
"plain_summary": "On 148 occasions in the last 24 hours, Reddit stopped sending
older posts before we reached the point where the previous check finished.
At least 90655 posts were published across those stretches, and we saved that
many. Going by how fast these feeds normally move, roughly 39676 posts were
missed. That figure is an estimate rather than a count, and it is more likely
to be too high than too low. Posts we did not reach cannot be fetched again."
}
Publishing that is uncomfortable and it is the point of this post. A sitewide keyword watch on the busiest listing Reddit exposes cannot keep up with that listing during its peaks, and pretending otherwise would be the easy option. What the instrument does instead is name the event, count it, estimate the loss, and then volunteer that its own estimate leans high and that a rate model contradicted part of the observation. An estimate that tells you which way it is wrong is worth more than a confident number.
The status field has four values and the difference between them is a difference in what is known, not in how bad things are:
| Status | Means | Was data lost |
|---|---|---|
complete |
Every poll reached the point the previous poll finished | No |
degraded |
At least one poll was truncated before catching up | Yes, and it cannot be refetched |
partial |
Nothing found, but not every kind of loss was checked | Not established, see unobserved_events |
unknown |
The check could not be run at all | Not established, see reason |
partial and unknown are not softer versions of degraded. They are the instrument declining to make a claim, which is the correct behaviour when it cannot see. Treating an unrun check as a pass is exactly how a monitoring system reports green over a live gap, so read the status word rather than the absence of a gap count.
Two operational consequences follow. First, a sitewide keyword watch is the right tool for a distinctive term and the wrong tool for a common one, because the loss lands on whatever was published during the peak minutes and a common term is likeliest to be published then. Second, if coverage matters more than breadth for your use case, name your subreddits. A named subreddit is its own poll with its own pace, and a quiet subreddit at a 30 second cadence has no realistic way to overflow.
There is one more counter in the same response that explains part of the picture, and it is unusually candid:
"poll_capacity": {
"streams_registered": 75,
"streams_per_cycle_max": 40,
"shortfall": 35,
"cadence_inflation": 1.88,
"status": "over_subscribed",
"cap_loss_observed": false,
"due_polls_missed": null,
"plain_summary": "Our poller checks up to 40 feeds per pass and is currently
working through 75, so 35 of them wait for a later pass. Your real check
interval is up to 1.88 times longer than the one you set. ... We have not
measured whether that ceiling has actually made any check late, so we are
not claiming either way."
}
Note the last sentence. The system reports a capacity shortfall, quantifies the inflation, and then explicitly refuses to claim the shortfall caused a late check, because it did not measure that. cap_loss_observed: false and due_polls_missed: null are two different statements: the first says no loss was seen, the second says the question was not answered. A monitoring product that keeps those separate is a monitoring product you can reason about.
Why suppressed counts two different faults
suppressed_24h is a single counter over two unrelated causes, and reading it as one thing is the most likely way to spend money you do not need to spend. A suppressed delivery is a match that was found and deliberately not sent, and there are two reasons that happens.
The first is the delivery ceiling: the monitor hit its daily cap, which resets at midnight UTC. The second is staleness: the item was already older than the freshness window when the poller first saw it, so it was withheld rather than delivered as though it were new. Those are opposites in every way that matters to you.
delivery_ceiling |
stale_item |
|
|---|---|---|
| Cause | Your monitor hit its daily cap | The item was old when first seen |
Sets ceiling_reached |
Yes | No, deliberately |
| Is it a fault | No, a plan limit doing its job | No, a freshness gate doing its job |
| Fixed by upgrading | Yes | No |
| Recoverable by retrying | No | No |
The response splits them for you rather than making you guess:
"suppressed_24h": 0,
"suppressed_breakdown": {
"resolved": true, "unresolved_reason": null,
"ceiling": 0, "stale": 0, "unknown": 0,
"sampled": 0, "sample_truncated": false
},
"ceiling_reached": false,
"daily_delivery_ceiling": 25000
Three rules for reading it. Do not treat suppressed_24h > 0 as evidence you are over your limit; ceiling_reached is the field that answers that question, and a stale withhold deliberately does not set it. When resolved is false the counts do not add up to the total and unresolved_reason says why, so do not report a split off a breakdown that has told you it is incomplete. And be aware the breakdown is built from a sample, with sampled and sample_truncated telling you how much of one.
There is a cap on the recording itself, and it is documented rather than hidden: only the first 500 suppressions per monitor per UTC day are persisted as rows. Past that point suppressed_24h is a floor rather than a count. On a monitor that is comfortably under its ceiling this never matters; on a monitor that blew through a cap by an order of magnitude, the number you read understates the number that happened.
The most useful thing about the stale category is what it explains. A monitor whose delivered count sits far below its matched count, with no error anywhere, no failing webhook and no plan limit rejecting anything, is usually a monitor withholding stale items. Before this field existed, that shape was indistinguishable from a bug.
The cheapest Reddit API. Try it free.
Reads from $0.002 per call. $0.50 free credits. No credit card required.
Is your monitor polled at the cadence you are paying for?
The cadence you set and the cadence you get are separate numbers, and the health endpoint reports both plus a verdict. promised_cadence_s is your plan's floor, requested_cadence_s is what this monitor asked for, and meets_entitlement is a boolean saying whether the second honours the first. That third field is the one worth alerting on.
The failure mode it exists to catch has already appeared in this post: a monitor created on a slower plan keeps its interval after the account upgrades, because a plan change does not silently rewrite monitors you did not touch. That is defensible behaviour and it is also invisible, so the API states it rather than leaving you to notice a latency figure that never improved. The full sub-object:
promised_cadence_sandrequested_cadence_s, the two numbers being comparedmeets_entitlement, the verdict, plusentitlement_noteexplaining a falselast_checked_s_ago, how long since the slowest stream feeding this monitor was polledfreshness_ratioandwithin_margin, that age as a fraction of the cadence, against a 1.25 marginslowest_stream, naming which stream is the constraintfreshness_reading, which states in words that this is an instantaneous reading rather than an average
That last field is the sort of thing that only exists if somebody has been burned by the alternative. freshness_reading reads: "instantaneous: the age of the most recent poll on the slowest stream feeding this monitor, not an average and not a sustained verdict." A single reading of 0.75 does not mean your monitor has been healthy all day, and the response says so in the response rather than in a documentation page nobody opens.
A deliberately slower cadence is also a legitimate choice, so a false meets_entitlement is stated and never corrected automatically. If you want it fixed, set cadence_s on the monitor and it takes effect from the next cycle. Worth knowing before you tune it: a stream watched by several monitors is polled at the fastest cadence any of them asked for, so lowering one monitor's interval can speed up a subreddit for other monitors too, and raising it may not slow anything down.
Matching semantics: why a long natural phrase never fires
Keyword matching on a push monitor is a literal, case-insensitive substring test against the fields you name, with no word boundaries, no stemming and no semantic understanding. That single sentence explains most of the disappointment people report with keyword alerts, in both directions: a short term fires far more than expected, and a long conversational phrase fires never.
An operator worked this out independently and wrote it down better than most vendor documentation does:

Pritesh
@MannPriteshh
reddit monitoring costs me $0 and 1,136 posts arrived by themselves last week no scraping, no API fees, no polling loop the plumbing: a free keyword alert service watches reddit for exact phrases new posts get pushed to a webhook the moment they are published my agent scores
"push alerts match literal substrings, so a short keyword fires constantly and "anyone recommend a good crm for small teams" never fires at all"
He is exactly right, and his prescription is the correct one: "short exact keywords for push streams. long natural phrases only where search understands meaning." A push monitor and a search query are different instruments and the same string does not belong in both.
Because there are no word boundaries, a monitor on ai matches "said", "maintain" and "chain". A monitor on go matches most of the English language. This is not a bug you can configure away, it is the matching model, and it is why the filter fields exist. The composition available to you is four fields combined with AND:
| Field | Semantics | Use it for |
|---|---|---|
q |
One substring that must be present | The anchor term |
include_all |
Every term must be present | Narrowing an ambiguous anchor |
include_any |
At least one term must be present | Spelling and product-name variants |
exclude_terms |
No term may be present | Killing a known noise source |
All four are ANDed together, and each list holds up to 50 terms of up to 200 characters. What that gives you is a conjunction of one substring test, one AND set, one OR set and one NOT set. What it does not give you is a query language: there is no quoting, no parentheses and no operator parsing, so an expression like (a AND b) OR c cannot be written. If you need that shape, run two monitors.
A worked example. Watching for ai alone is unusable. Watching for ai with include_all: ["agent"] and exclude_terms: ["said", "again"] is still a text hack. The better move is to pick an anchor that is distinctive on its own, a product name or a phrase like reddit api pricing, and use include_any for its real variants. Distinctiveness in the anchor does more work than any amount of filtering after it.
Field scope matters as much as term choice. search_in defaults to ["title", "body", "url"] and accepts permalink as a fourth option. Two behaviours to know:
- On a comment,
titleresolves to the title of the parent thread, because a comment has no title of its own. Under the default scope, that means a busy thread whose title contains your term can deliver every comment beneath it. If you only want comments that themselves say the term, scope to["body"]. - Matching against
urlcatches terms that appear in a link slug but nowhere in the visible post. That is occasionally what you want and frequently a false positive source, so narrowing to["title", "body"]is a reasonable default for brand monitoring.
Two more filters that behave less obviously than they read. nsfw is not exclude by default: omitting it or setting it true both allow adult content through, and only an explicit false filters, which is also rejected on a comment monitor because Reddit flags posts rather than individual comments. And min_score passes any item whose score field is missing, so a filter that looks strict is permissive on brand new posts, which is precisely the population a monitor sees. A minimum score on a real-time monitor mostly filters out the posts you wanted first.
The delivered payload carries an enrichment block with relevance, intent and sentiment, each declaring its method as a heuristic. Use it to sort your queue, not to decide what reaches a human.
Sitewide versus named subreddits, and what each one costs
A monitor is anchored either by a keyword across all of Reddit or by a list of named subreddits, and the choice changes coverage, cost and what is possible. A sitewide monitor omits subreddit and supplies q. A scoped monitor names 1 to 50 subreddits. Passing subreddit: ["all"] is refused, because r/all is Reddit's aggregate listing rather than a community, and the sitewide path already covers it properly.
The difference that matters commercially is where the cost lands. One shared stream serves every sitewide monitor on the platform, so the second sitewide monitor costs no extra Reddit traffic at all. A named subreddit is its own stream with its own polling cost, which scales with how many distinct subreddits are watched rather than with how many monitors exist. That asymmetry is why the plan allowances count three separate things:
- Monitor slots, how many watches you may run at once. A paused monitor still holds its slot.
- Distinct subreddits, counted across your whole account, not per monitor. The same subreddit named in two monitors counts once.
- Sitewide slots, a separate and smaller allowance, drawn from your monitor slots rather than added to them.
Practical consequences:
- Comment monitors must name a subreddit. A sitewide comment watch is refused outright, and the refusal is honest about why: the sitewide comment listing moves too fast to page reliably at any affordable cadence, so offering it would mean quietly missing most of it. Refusing is better than a comment monitor whose coverage is fiction.
- Comment volume runs about seven times post volume. A comment monitor on a busy subreddit approaches its daily delivery ceiling far faster than a post monitor on the same community. Check the ceiling before enabling it, not after.
- Excluding subreddits is a sitewide-only tool.
exclude_subredditsis the noise control for an all-of-Reddit watch and is rejected if you also passedsubreddit, because a scoped monitor should just drop the name from its list. It filters delivery only: the poll still happens and nothing is freed. - Scoped monitors get better coverage. A quiet subreddit at a 30 second cadence has no realistic way to overflow its listing, which is the failure documented earlier. If completeness matters more than reach, name your subreddits.
That fourth point is worth putting against the contrary view, which has real support. Plenty of practitioners argue for narrowing on precision grounds rather than coverage grounds, and one vendor in the r/SaaS survey thread made the strongest version of the case for context over breadth:
"It scans Reddit hourly and uses semantic understanding rather than only keyword matching, so it catches relevant conversations even when they don't use the obvious terminology."
Both arguments land in the same place from different directions. Narrow when you know where your buyers are. Go sitewide when you are watching for a distinctive string and would rather see it wherever it appears, accepting that the busiest minutes are where losses concentrate. Running one of each, with the sitewide monitor holding your most distinctive term, is a reasonable default.
There is one more asymmetry worth knowing if you deliver to Slack or Discord. Setting group on a monitor bundles several matches into one delivery instead of one call per match, which is a good idea for a noisy term. On a generic webhook the bundle carries the full items array. On Slack and Discord, the message carries the first item plus a count of the rest. If your team reads a grouped monitor in a Slack channel, they are seeing one post and a number, not the list. Send grouped monitors to a generic webhook and format them yourself, or leave grouping off for Slack targets.
What does Reddit monitoring cost per month?
Monitoring is billed by plan, not by activity. All ten management endpoints are plan billed rather than call billed, deliveries and retries are not billed at all, and no API credits are consumed by monitoring in any form. The bill therefore does not move with how loud your keyword turns out to be, which is the property that makes the cost forecastable.
Read the current numbers from the pricing page rather than from this or any article, because a stale price is the most expensive kind of error a post like this can carry. As read from the deployed pricing surface on 2026-08-31:
| Plan | Monthly | Monitors | Distinct subreddits | Sitewide watches | Cadence | Comments | Daily cap per monitor |
|---|---|---|---|---|---|---|---|
| Free | $0, no card | 1 | 0, sitewide only | 1 | about 60s | No | 10,000 |
| Starter | $19 | 15 | 10 | 1 | about 60s | No | 10,000 |
| Growth | $49 | 50 | 25 | 3 | about 30s | Yes | 25,000 |
| Pro | $99 | 200 | 50 | 10 | about 30s | Yes | 50,000 |
| Scale | $199 | 500 fair use | 100 | 25 | about 30s | Yes | 100,000 |
The free entitlement is unusual enough to be worth stating precisely, because it is the thing that lets you test everything in this post without a card: one keyword watched across all of Reddit, posts only, at about a 60 second cadence, delivered to your webhook, Slack or Discord exactly as a paid monitor is, with a 10,000 alert daily cap. Naming a subreddit, matching comments, a faster cadence and any second watch all require a plan. The reason for that split is the cost asymmetry described above and not an arbitrary paywall: sitewide watches share one stream, so a thousand of them cost what one costs, while a thousand free accounts each naming a different subreddit would be a thousand separate polls.
Note that Scale is presented as unlimited monitors with a fair use qualifier, and the enforced number is 500 at a time. The headline and the enforcement differ, so plan against 500.
Two cost comparisons are worth making explicitly. Against per-match pricing, a flat plan wins whenever a term is loud and loses whenever it is quiet, so the question is not which is cheaper but which failure you prefer: a bill that spikes when your brand has a bad week, or a floor you pay in a quiet month. Against building it yourself, the read calls for a polling loop cost $0.002 each on this API, a figure read from the pricing page on 2026-08-31, so a 10 minute poll is a few dollars a month per query and genuinely cheaper in raw call spend. What the plan buys is not the calls; it is signed delivery, the retry ladder, the delivery ledger, the ceiling and the coverage measurement. Price the engineering, not the requests. Model your own figures with the cost calculator and the Reddit API pricing breakdown.
Hosted monitor versus your own polling loop in 2026
The build versus buy question for Reddit monitoring changed shape in 2026, and the change is not about price. Building a polling loop was never hard and is not hard now: it is a scheduled search call, a set of seen identifiers, and an alert. We published the whole thing as a Python keyword monitor and the core is about fifty lines. Nothing in this post argues you should not write it.
What changed is that the category grew a set of expectations that take months rather than an afternoon. Signed delivery. A retry policy that distinguishes a transient auth failure from a refused payload. A queryable ledger of what was sent and what died. A daily ceiling that fails safe. And an honest account of what the poller did not fetch. A loop you wrote last year has none of those unless you built them, and the reason to know that is not to feel bad about the loop, it is to know which one you are missing when something goes quiet.
The four competing surfaces are Octolens, Syften, Redreach and KWatch, plus the free floor everyone starts on, F5Bot. Here is what those surfaces actually tell a buyer about delivery:
What each Reddit monitoring surface tells you about delivery
| Surface | Signed payload documented | Retry schedule published | Missed-item figure exposed | Source |
|---|---|---|---|---|
| This API | yes, HMAC-SHA256 with a stated window | yes, six attempts | yes, on the health endpoint | measured |
| Octolens product page | not stated on the page | not stated on the page | not stated on the page | published |
| Syften product page | not stated on the page | not stated on the page | not stated on the page | published |
| Redreach webhook page | not stated on the page | not stated on the page | not stated on the page | published |
| n8n Reddit trigger | not applicable, it polls | not applicable | not stated on the page | published |
Read that table narrowly. Every competitor row records what that vendor's own public page said on the date given, and "not stated on the page" is a claim about the page, never a claim that the capability is absent. Several of these products may implement all three and simply not publish them. The finding is that a buyer comparing these surfaces cannot read the delivery contract off any of them, which is a documentation gap in the category rather than an accusation about any one product.
The category is also moving quickly, and the vendors themselves are the evidence. The founder of one of the tools ranking on this exact query dated his own webhook support to April 2026:

Dominik Sobe ツ
@sobedominik
Redreach now supports Email, Slack, Telegram and Webhook notifications for Reddit alerts. Super excited for this! People have been asking for this for a while but I had some technical debt to clear first which took a bit of time. Now everything's prepared for even faster ships… Show more

"Redreach now supports Email, Slack, Telegram and Webhook notifications for Reddit alerts."
Webhook delivery in Reddit monitoring is a recent and contested feature, not settled infrastructure. That is precisely when it pays to ask what the delivery guarantees are rather than assuming a category norm exists.
One warning about a specific misconception, because it sits at position two on this search result and costs people real time. The Reddit trigger offered by general automation platforms looks like a webhook in a workflow builder, and it polls Reddit on a schedule and then fires your downstream step. You inherit interval-based latency and the platform's own trigger cadence limits. They are convenient for low-volume automations and they are not a push channel, because there is no push channel to wrap.
If you want the general engineering background before deciding, this walkthrough of how webhooks replace polling covers the event-delivery model itself, independent of any Reddit specifics:
The most useful buying criterion in the whole r/SaaS survey thread came from someone describing what a rival does when a keyword gets loud:
"Yea they delete keywords if matches are over a certain threshold."
That is the alternative design to a daily ceiling, and it is worth understanding before you pick either. A ceiling stops sending for the rest of the UTC day and keeps recording, so you can see what was held back and the monitor resumes on its own. Deleting the keyword stops the monitor permanently and silently until a human notices. Another reply in the same thread described a third variant, a temporary rate limit that lifts on a higher plan. Ask which of the three your vendor does, because all three are called the same thing in marketing copy.
What should you check before you trust a monitor?
A monitor deserves trust once you have confirmed the four things it can fail at independently: that it delivers, that the delivery is authentic, that you know what it withheld, and that you know what it never fetched. Every one of those maps to a field the API already returns, so the whole pass is a handful of calls and none of them costs a credit.
Work through it in this order:
- Send a test delivery with
webhook/testand readdeliveredin the body rather than the HTTP status, which is 200 either way. Do this before waiting on a real match. - Verify the signature on your side, over the raw bytes, using the two-part header. A handler that accepts unsigned requests is an open endpoint that anyone who learns the URL can post to.
- Enforce the 300 second window yourself, in both directions. Rejecting only stale timestamps leaves a clock-skewed future timestamp valid indefinitely.
- Deduplicate on
x-redditapis-delivery-id. Delivery is at least once, so a duplicate is expected behaviour rather than a bug, and the identifier is a header precisely so you can discard one before parsing. - Read
coverage_24h.statusand treat anything other thancompleteas a finding.partialandunknownmean the check could not answer, which is not the same as a pass. - Split
suppressed_24hon its breakdown. A ceiling suppression is fixed by upgrading; a stale suppression is not, and confusing them is how you buy a plan you did not need. - Check
cadence.meets_entitlement. A monitor created on an older plan keeps its old interval, so the latency you are getting may not be the latency you are paying for. - Watch
consecutive_failuresonwebhook/list. A dead endpoint is silent by design, and this is the counter that stops silence looking like calm.
Two habits are worth adding on top. Alert on the absence of deliveries, not only on their failure, because a monitor that stops matching produces no errors at all. And remember last_match_at is windowed to the last 24 hours: a monitor quiet for longer reports null rather than its true last match, so do not build a staleness alarm on it without knowing that.
The honest summary of everything above is short. Reddit does not push, so somebody polls, and the only question worth asking a monitoring vendor is what their poller guarantees and how you would find out if it stopped. This API answers that with a signed payload, a six attempt ladder over about twenty minutes, a readable delivery ledger, and a coverage figure that will tell you it lost 39,676 posts on a bad day rather than reporting green over the gap. This API also has real edges, and they are named here rather than buried: a dead delivery cannot be replayed, history is kept for 14 days, keyword matching is a raw substring test, and a sitewide watch on a common term will lose data during peaks.
If you want to try the whole verification pass without spending anything, the free entitlement covers it: one all-of-Reddit keyword watch, a real webhook, real signatures, real health data. Create an account at signup, read the full API documentation, and point a monitor at a term you know appears a few times a day. The instrument is only useful if you have looked at what it says about itself.
Where these numbers come from.
Each row is a figure in this post and the artefact it was read from. Reddit's access rules and the third-party archives around them keep moving, so check the date on a source before you build against it.
- Reddit Data API Wiki
- Reddit's own rate-limit and access documentation, the reason every Reddit feed is poll based rather than push based.
- Reddit API documentation
- The official endpoint reference. Carries no webhook or callback registration surface of any kind.
- PRAW documentation
- The stream helpers whose polling implementation this post contrasts against hosted delivery.
- webhooks.fyi
- Vendor-neutral webhook best-practice reference, used for the signature and replay-window conventions.
- Node.js crypto documentation
- createHmac and timingSafeEqual, the two primitives the verification snippet uses.
- u/gawiz93, r/SaaS
- Reports that a competing monitoring tool deletes a keyword outright once its match count crosses a threshold, the alternative design to a daily delivery ceiling.
- r/SaaS, founder survey thread on Reddit monitoring tools
- A founder asking for real experiences rather than marketing claims, and the replies naming what each tool does and does not do.
- @MannPriteshh on X
- An operator independently deriving that push alerts match literal substrings and that long natural phrases never fire.
- redditapis.com pricing page
- The deployed source for every monitoring price, slot count, cadence and delivery ceiling quoted in this post.
Frequently asked questions.
No. The Reddit Data API exposes no server-push mechanism: there is no endpoint where you register a callback URL and have Reddit notify you when a post appears. Every near-real-time Reddit feed is polling underneath, including PRAW's stream helpers and the Reddit triggers in automation platforms. What a hosted monitoring product gives you is a webhook on ITS side: it runs the poll, matches your keyword, and pushes the match to your URL. The push is real, the source of the push is not Reddit. See webhooks versus polling for Reddit for the underlying mechanics.
Recompute an HMAC-SHA256 over the string ${timestamp}.${rawBody} using the signing secret you were shown once at webhook creation, then compare it in constant time against the v1= value in the x-redditapis-signature header. The header carries both parts, formatted t=<unix seconds>,v1=<hex digest>. Reject any request whose timestamp is more than 300 seconds away from your own clock in either direction, since a future-dated timestamp would otherwise stay valid forever. Sign over the RAW body bytes, not a re-serialised object. Full worked snippets in the verification section.
The delivery is retried on a fixed ladder of 5 seconds, 30 seconds, 2 minutes, 5 minutes and 13 minutes, which is six attempts including the first over roughly 21 minutes, with jitter applied so every customer's retries do not land on the same second after a shared incident. If all six fail the delivery record is marked dead and stays readable through the deliveries endpoint with the error your server returned. Nothing is silently dropped, but nothing is redelivered after the ladder ends either, so a dead row is yours to reconcile. See what happens when your endpoint is down.
Per month, by plan. The ten monitoring management endpoints are plan billed rather than call billed, so listing your monitors, reading their health and paging through delivery history draw no API credits at all. Deliveries and retries are not billed either. A free entitlement of one all-of-Reddit keyword watch is included on every account with no card, and the paid ladder runs $19, $49, $99 and $199 a month for progressively more watches, more distinct subreddits and a faster cadence. Read the current figures on the pricing page, never from an article, because a price is the most expensive thing to get stale.
Read coverage_24h on the monitor health endpoint. Its status is complete, degraded, partial or unknown, and only complete means every poll reached the point where the previous poll finished. A degraded status means at least one poll was truncated before it caught up, which is unrecoverable loss, and the response carries gap_events_24h, posts_in_window_at_least and an estimated posts_missed_estimate. This is the number most monitoring tools do not expose, because reporting what you missed requires measuring it. See what did the monitor miss.
Because push matching is literal. A monitor's keyword is matched against the fields named in search_in, which default to title, body and url, as a text match rather than as a semantic search. A short distinctive phrase like a product name fires reliably; a long conversational sentence almost never appears verbatim in a real post, so it matches nothing while looking perfectly reasonable in the configuration screen. Use short exact terms for a push monitor and save long natural phrasing for a search query, which is a different instrument.
Yes, with two constraints worth knowing before you build on it. A comment monitor has to name at least one subreddit, because an all-of-Reddit comment stream cannot be paginated fast enough to be honest about coverage at any affordable cadence. Comment volume also runs roughly seven times post volume, so a comment monitor on a busy subreddit will approach a daily delivery ceiling far faster than a post monitor on the same community. Comment monitoring sits on the Growth tier and above. See sitewide versus named subreddits.
It depends on whether you want to own an operational problem or a bill. Your own loop costs a few dollars a month in read calls and gives you total control over matching, and it is genuinely about fifty lines to start. What it does not give you for free is signed delivery, a retry ladder, a delivery ledger, a daily ceiling, and an honest account of what the poller failed to fetch. Those are the parts that take months rather than an afternoon. If you already have the loop and it works, keep it. See the build your own keyword monitor guide for that path.
No, at least once. Every delivery carries an x-redditapis-delivery-id header holding a stable identifier for that specific delivery, and the correct integration keys on it: record the id, and if you see it again, discard the duplicate rather than reprocessing. At-least-once without a stable id is just sometimes-twice, which is why the id is a header rather than an optional field. A single Reddit match also produces one delivery row per registered webhook target, so two webhooks on one account means two rows for the same post, each with its own delivery id and its own outcome. See reading a delivery record.
Keep reading.
Continue exploring related pages.
Reddit API documentation
The complete 2026 reference: auth, all 52 endpoints, and code.
Get a Reddit API key
Instant bearer token, no waitlist and no enterprise contract.
Reddit Responsible Builder Policy
Why Reddit denies API applications, and the managed REST bypass.
Reddit API use cases
14 use cases from AI training to brand monitoring and DMs.
Reddit Search API
Search posts, comments, users, and communities over one REST endpoint.
Reddit MCP server
Wrap the REST API as MCP tools for Claude, Cursor, and any MCP client.
Reddit API for AI agents
Live Reddit context for tool calls, MCP servers, and RAG pipelines.
Redditapis pricing
Endpoint-level costs and quick monthly totals - reads from $0.002 / call.
Reddit API cost calculator
Estimate monthly spend using your request volume.
Reddit API guides and tutorials
Tutorials, walkthroughs, and API deep-dives for developers.
Reddit API alternatives
Evaluate alternatives by cost model, limits, and integration fit.
Cheap Reddit API
The cheapest way to get Reddit data: $0.002 per call, no contract, no minimum.
Official Reddit API vs Redditapis
Access, setup, rate limits, and pricing, side by side.
PRAW alternative
A hosted Reddit REST API for any language, no app registration or OAuth.
Reddapi alternative
A maintained Reddit REST API with published pricing and write endpoints.
Reddit comment scraper alternative
The raw comment API: search and filter comments, historical and live, clean JSON.
Reddit scraper API
Hosted scraper API vs building your own: managed proxies, clean JSON.
RapidAPI Reddit alternative
A direct, maintained Reddit API with published pricing and write endpoints.
Bright Data Reddit alternative
A purpose-built Reddit API vs a general scraping platform: structured JSON, plus writes.
ScraperAPI Reddit alternative
A Reddit-native API vs a generic HTML fetcher: auth and pagination handled, typed JSON.
TikHub alternative
TikHub's Reddit surface is read-only; get comment, vote, and DM endpoints too.
EnsembleData alternative
No $100/month floor: pay per call from $0.002, plus write, vote, and DM endpoints.
Scrape Creators alternative
7 read-only Reddit endpoints vs a dedicated API with real write, vote, and DM paths.
FetchLayer alternative
Posts, comments, and search only; add vote, comment, and DM over the same REST auth.
Reddit monitoring API
Build your own keyword and brand-mention monitor: search, comment search, and subreddit streams over REST.
F5Bot vs Redditapis
F5Bot's Slack and Discord delivery needs its $49.99/mo Gold tier; Redditapis includes it from $19/mo.
Syften vs Redditapis
Syften caps you at 100 to 500 results a day; Redditapis allows 10,000 a day per monitor at the entry plan.
Octolens vs Redditapis
Octolens meters by mention with overage fees; Redditapis is flat-priced by subreddit slot from $19/mo.
Affiliate program
Earn 20% lifetime commissions - capped at $5,000/yr.
Reddit Vote API tutorial
Upvote and downvote a post programmatically via the REST API.
Reddit Data API: REST, no PRAW
REST endpoints for Reddit data with no PRAW and no OAuth dance.
Reddit scraping benchmarks
Real throughput, error rates, and cost benchmarks for Reddit scraping.
Reddit API answers
Direct answers on cost, access, rate limits, endpoints, and auth.
How much the Reddit API costs
Per-call pricing from $0.002 a read, with $0.50 in free credits.
Reddit API in Python
One requests call with a bearer token, no PRAW and no OAuth flow.
Reddit shadowban checker
Check if a Reddit account is shadowbanned in seconds, free and no login.
Similar reads.
More guides on the Reddit API, scraping, pricing, and MCP servers.








