Detect a sudden spike in negative reviews

Routine that watches review events with rating ≤ 2 and pages when the rate exceeds 3x baseline. Catch coordinated review-bombing OR a quality regression in 15 minutes.

Published

difficulty intermediate · time to value 10 minutes · tools used 2

Just say this

Watch review_submitted events with rating ≤ 2. Alert me if the rate exceeds 3× the 7-day baseline. Run as a Routine every 15 minutes.

Why this matters

Negative review spikes are one of those signals that’s catastrophic if you miss it and trivial if you catch it early:

  • Coordinated review-bomb: a competitor, a disgruntled customer with a Twitter following, or a flash mob (some product appearance on TV inviting “go review this and tank it”). The longer it runs, the more it sets the App Store / Trustpilot baseline.
  • Quality regression: a deploy broke a checkout flow, customers paid, didn’t get their order, are now 1-starring you. The window between “first 1-star comes in” and “fix shipped” is the entire damage radius.

Both look identical: a 1-2 star rate that’s 3-10× the normal rate. Both need a human eyeball within 15 minutes, not 15 hours.

Agentry’s Routines + raw HogQL is the simplest way to build this. No Datadog, no PagerDuty config files — just a scheduled prompt that checks the rate and pings Slack when it spikes.

What you get

  • A scheduled Routine running every 15 minutes
  • A clean baseline (7-day rolling avg, ignoring outliers)
  • A Slack message when the current 15-minute window exceeds 3× baseline, with offending sellers/listings included

Walk through it

You

Watch review_submitted events with rating ≤ 2. Alert me if the 15-minute rate exceeds 3× the 7-day baseline. Post to my Slack webhook https://hooks.slack.com/services/T0000/B0000/XXXX. Run every 15 minutes.

Agent

I’ll register the webhook first, then schedule the Routine.

agentry_register_webhook {
  project_id: "default",
  url: "https://hooks.slack.com/services/T0000/B0000/XXXX",
  description: "Review-bomb detector",
  events: ["routine.review_bomb_alert"]
}
{
  "webhook_id": "wh_rb1",
  "signing_secret": "agws_…(shown once)…",
  "active": true
}
Agent

Now scheduling the Routine. The prompt below runs every 15 min — it computes baseline + current window, compares, posts if over threshold.

/schedule "*/15 * * * *" "
Step 1: Compute baseline. Run agentry_analytics_query:
  SELECT countIf(properties.rating <= 2) / (7 * 24 * 4) AS baseline_per_15min
  FROM events WHERE event = 'review_submitted'
  AND timestamp BETWEEN now() - INTERVAL 7 DAY AND now() - INTERVAL 15 MINUTE

Step 2: Compute current 15-min window:
  SELECT countIf(properties.rating <= 2) AS current_window,
         groupArray((properties.seller_id, properties.rating, properties.comment)) AS offending
  FROM events WHERE event = 'review_submitted' AND timestamp > now() - INTERVAL 15 MINUTE

Step 3: If current_window > 3 * baseline_per_15min AND current_window >= 5, POST a Slack message
to webhook wh_rb1 with current rate, baseline, top offending sellers, and a link to investigate.
Otherwise silent.
"
{ "routine_id": "rt_rb1", "next_run_at": "2026-05-16T13:30:00Z" }
Agent

Done. Routine rt_rb1 runs at :00, :15, :30, :45 each hour. Per run:

  1. Computes 7-day baseline (low-rating reviews per 15 min)
  2. Counts the current 15-min low-rating reviews
  3. If current is > 3× baseline AND ≥ 5 reviews (so we don’t page on tiny windows), posts to Slack

Silent if no spike. First check fires in ~15 min — I’ll let you know how the baseline math came out on the first run.

One safety: the current_window >= 5 floor prevents 2 low reviews from triggering when baseline is 0.5. If your normal volume is much higher, raise the floor proportionally.

The output

A Slack message like:

🚨  Negative review spike

  Last 15 min:    42 reviews with rating ≤ 2
  7-day baseline: 3.8 / 15 min
  Multiplier:     11.1× ★

  Top affected sellers (last 15 min):
    seller_8421   "Order never arrived"      ×12
    seller_3110   "Item not as described"    ×8
    seller_8421   "Charged but no order"     ×6

  Likely: either coordinated bomb on seller_8421, OR
          a fulfillment / order-state regression.

  Investigate:
    https://agentry.sh/recipes/dispute-rate-monitoring
    https://agentry.sh/recipes/customer-investigation

And as a console output when the Routine first runs:

review_bomb_detector Routine (rt_rb1)

  ✓ Webhook registered:   wh_rb1
  ✓ Cron schedule:        */15 * * * *
  ✓ Floor threshold:      ≥ 5 events in current window
  ✓ Multiplier threshold: > 3× baseline
  ✓ First check at:       2026-05-16T13:30:00Z

  Baseline (computed at register time):
    Last 7 days low-rating reviews:    2,560
    Average per 15-min slot:           3.81
    P95 per 15-min slot:                12

Setting it up

1. Emit review_submitted with rating. The agent groups by seller_id to surface offenders — include it if you have it:

await fetch(`https://api.agentry.sh/v1/analytics/${PROJECT_ID}/`, {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.AGENTRY_DSN}`,
    "Content-Type": "application/json",
    "User-Agent": "marketplace/1.0",  // REQUIRED — Cloudflare 403s default UAs
  },
  body: JSON.stringify({
    event: "review_submitted",
    distinct_id: reviewer.id,
    properties: {
      rating: review.rating,           // 1-5
      seller_id: review.seller_id,
      listing_id: review.listing_id,
      comment: review.comment?.slice(0, 200),  // capped — don't ship full essays through analytics
    },
  }),
});

2. Webhook verification (optional but recommended). Agentry signs all webhook payloads — verify in your Slack-side proxy:

async function verifyAgentryWebhook(req: Request): Promise<boolean> {
  const sig = req.headers.get("x-agentry-signature") ?? "";
  const body = await req.text();
  const key = await crypto.subtle.importKey(
    "raw",
    new TextEncoder().encode(process.env.AGENTRY_WEBHOOK_SECRET!),
    { name: "HMAC", hash: "SHA-256" },
    false,
    ["sign"],
  );
  const mac = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(body));
  const expected = Array.from(new Uint8Array(mac))
    .map((b) => b.toString(16).padStart(2, "0"))
    .join("");
  return sig === expected;
}

3. Tune the threshold. Default 3× over baseline + floor of 5 is conservative — alerts ~once per month on a healthy marketplace. If you’re paging too often, raise to 5×; too rarely, lower to 2×.

Variations

  • “Add a per-seller spike detector too: any single seller getting >5 negative reviews in 1 hour, page seller-support.”
  • “Cross-reference the spike against recent deploys — if a deploy was in the last 4h, link to the deploy in the Slack message so engineering can roll back.”
  • “Run the same logic but for dispute_opened events — fraudulent buyers sometimes mass-dispute too.”
  • “During the spike, auto-mark the affected listings as ‘in review’ and stop new payments until a human looks.”

Try this recipe in your own agent.

Paste the prompt above into your agent. It'll set up Agentry against your data, then run the recipe and show you the real output.

Install https://agentry.sh/agentry.md and set it up