Engineering

When Bot Detection Is Wrong: the Mark-It-Human Override

Every classifier is sometimes wrong. What matters is whether the product gives you a correction path — and whether that correction actually wins.

July 2026 • 10 min read

Bot detection vendors love to talk about accuracy. Almost none of them talk about what happens when they're wrong — and every classifier, including ours, is sometimes wrong. A screen-reader user gets lumped in with scrapers. A keyboard-driven power user trips the anomaly detector. A real customer on a corporate VPN reads as datacenter traffic.

ClickStream's position is that false positive bot detection is not an edge case to hide — it's a certainty to design for. This post walks through the correction machinery we ship: the operator mark-it-human override, its reverse (force-bot), and a third lane — kiosk — for the digital signage traffic that is neither. All of it traces to the collector and dashboard source code, because a correction path you can't inspect is just another black box.

Why False Positive Bot Detection Happens

The uncomfortable truth about behavioral bot detection is that the signals that expose automation also show up in real people:

That distinction drives the whole design. Our internal evasion atlas states the rule directly: any hard-gate behavior on stealth classifications requires operator opt-in and a "these are my real users" override. The override is not an afterthought — the classifier shipped on the condition that it exists.

Two Detection Layers, One Composite Verdict

To understand what the override overrides, you need the shape of the pipeline. ClickStream classifies traffic in two layers:

Layer 1 — network, per request. The collector computes a composite bot score (0–100) from Cloudflare Bot Management, user-agent matching against a registry of 158 named bots across 11 categories (including 38 AI agents — the population we measure in our AI answer-engine crawler work), datacenter-IP detection, and the verified-bot flag. A score of 50 or above sets is_bot=1.

Layer 2 — behavioral, per session. A session-scoped process accumulates clicks, scrolls, form fills, mouse movement, and timing into a humanConfidence score that evolves as the session progresses.

The two reconcile through one canonical expression, shared by the write path, the live surfaces, and dashboard readers:

bot  =  isBot
     || (botScore >= 40 && humanConfidence < 30)
     || (botScore >= 30 && humanConfidence < 20)

On top of that, sessions whose stealth score reaches 60 get promoted to the stealth_bot category, and previously confirmed verdicts are cached per session and visitor so a bot doesn't flicker back to human between batches. Each of these mechanisms is right most of the time. Each one is also a place a real person can get stuck. Which is why the override has to beat all of them.

The Mark-It-Human Override

When you (or your support team, via a user complaint) find a real person classified as a bot, you mark the visitor human from the dashboard. Under the hood that's one API call:

POST /api/sites/{siteId}/bot-overrides

{
  "visitorId": "cs_k3PZ9qW1",
  "classification": "human",
  "reason": "Support ticket #4821 — screen-reader user"
}

The route writes a record into the collector's KV override lane, keyed per client and visitor:

botclass:{clientId}:{visitorId}  →  {
  "category": "human",       // 'human' | 'bot' | 'kiosk'
  "source":   "manual",      // dashboard operator override
  "reason":   "Support ticket #4821 — screen-reader user",
  "setAt":    1753628400000,
  "setBy":    "usr_82hf"     // dashboard user id — audit-logged
}

From that point, on every event batch the collector checks the override before trusting any automated verdict. A human classification forces is_bot=0 and clamps the stored bot score to 10 or below — deliberately under the review band, so every dashboard lane agrees with the flag instead of showing a "human" visitor with a suspicious score.

What the Override Wins Against

"Override" is a strong word, so here is the precedence order, verbatim from the ingest path's effective-verdict logic:

Priority Verdict source Effect
1 Operator override: human is_bot=0, score clamped ≤ 10
2 Operator override: bot is_bot=1, score ≥ 80
3 Operator/auto override: kiosk is_bot=1, score ≥ 95, stable kiosk identity
4 Cached confirmed bot verdicts (bot cache) is_bot=1, score ≥ 80
5 Composite behavioral verdict Flag flips per the expression above
6 Raw network score is_bot=1 at score ≥ 50

The human override sits above the bot cache, the behavioral composite, and the network score, and it explicitly suppresses stealth promotion — the ingest path skips the stealth-verdict pass for operator-marked humans, because the atlas's "these are my real users" correction must beat every automated lane. The collector also looks the override up under both identifier spaces a visitor lives in — the device UUID and the cs_ id — so a correction applies no matter which id the next event carries.

Propagation, Expiry, and the Audit Trail

Three operational details worth knowing:

One more property, easy to miss: the override lookup is fail-open. If KV is briefly unavailable, the collector returns "no classification" and keeps writing events rather than blocking ingest — the same fail-open posture we apply to billing.

The Reverse Lane: Force-Bot

The same endpoint accepts "classification": "bot" — the mirror-image correction for false negatives. A scraper running a clean residential proxy and a mainstream user agent can score human on every automated layer. If you know better (you recognize the crawl pattern, or the "visitor" is your own load test), you force the verdict: is_bot=1 with a score of at least 80. If no named bot in the registry matched, the events are stamped with a Manual Classification identity carrying your stated reason, so future-you knows why that traffic sits in the bot lane.

Because billing counts human pageviews only, force-bot has a billing consequence in your favor: traffic you classify as bot stops counting toward your plan's allowance.

The Kiosk Lane: Digital Signage Is Not a Person

The third classification exists because some traffic is neither a person to rescue nor a bot to banish. A showroom display, a lobby dashboard, an auto-refreshing status ticker — these are long-lived, anonymous devices generating endless pageviews with no purchase intent.

Marking a device kiosk (operators can set it manually; automated classifiers can also write kiosk records) forces a verdict of is_bot=1 with a score of at least 95 and a stable kiosk identity label. The consequences are deliberate:

Why not just mark the kiosk human? Because a shared display is automated traffic by any honest definition, and classifying it human would corrupt every downstream count — visitor totals, engagement scores, conversion rates. The kiosk lane keeps the data and the honesty.

Reading the Corrected Verdict from Page Code

Overrides don't just change dashboards — they flow into the same visitor snapshot your page code reads through the Signals API (@clickstreamhq/signals, currently a developer preview). Configuration always comes first:

import { configure, getVisitorOrNull } from '@clickstreamhq/signals';

configure({ apiKey: 'cs_live_xxx' });

const visitor = await getVisitorOrNull();
if (visitor && !visitor.bot.isBot) {
  // Human lane — personalize, count, enrich.
}

// After a mark-it-human override, the same visitor reads:
//   visitor.bot.isBot  → false
//   visitor.bot.score  → clamped to 10 or below

Note that visitor.bot and visitor.behavioralClass are separate fields by design: network classification and behavioral classification can legitimately disagree, and the API shows you both rather than papering over the tension. The Signals getting-started guide covers the full snapshot.

Honesty as Product

There's a pattern here that runs through ClickStream's design, and it's the same one behind confidence-banded identity resolution: when the system is uncertain or wrong, it should say so and hand you the steering wheel — not fake certainty.

Applied to bot detection, that means four commitments you can check against the source:

A classifier that admits it can be wrong — and ships the correction as a first-class, auditable feature — is more trustworthy than one that claims it never is.

If you're evaluating bot detection, ask every vendor the same question: when your classifier flags my best customer as a bot, what do I click, and what does it win against? If the answer is a support ticket, keep looking. Ours is a button, a 90-day KV record, and a documented precedence order — live within a minute. Installation is a single script tag if you want to see it on your own traffic.

See What Your Bot Traffic Really Is

158 named bots, 11 categories, behavioral detection for the rest — and a correction path for the day we get one wrong. Billing counts human pageviews only.

Start free