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:
- Power users. Fast, keyboard-heavy navigation with minimal mouse movement looks statistically abnormal. Our own metrics documentation is blunt about it: bot traffic often trips the behavioral anomaly detector, but so do power users, automated testing, and legitimate accessibility-tool workflows.
- Accessibility tools. Screen readers drive the page programmatically. Tablets used with styluses produce pointer traces whose entropy sits outside the bands calibrated on average mouse traffic. Our bot-evasion research notes both as populations that can require per-tenant threshold adjustments — precisely because they misfire on global defaults.
- Corporate proxies and VPNs. Network-level heuristics penalize datacenter IP ranges. A human browsing through a corporate egress point or a VPN that exits from a hosting ASN inherits that penalty without doing anything bot-like.
- Stealth-detection collateral. Our strongest signal against stealth browsers is mouse-movement entropy. In our internal test corpus it carries a sub-1% false-positive rate against human sessions — small until you translate it: roughly 80 real humans per 10,000 flagged sessions. Acceptable for a "suspicious" bucket; not acceptable for silently discarding 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:
- It applies within about a minute. The collector caches "no override" per isolate for 60 seconds, so a freshly written override reaches the ingest hot path on roughly that timescale.
- It expires after 90 days. The KV record carries a 90-day TTL. An override is a correction, not permanent configuration: if the same visitor keeps getting misclassified, that's a detection bug — or a case for per-site scoring calibration — not a flag to renew forever.
- It's accountable. Writes require an owner, admin, or editor role (viewers are read-only), and every create and delete is written to the audit log with who, when, and the stated reason. Deleting the override reverts the visitor to automatic detection.
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:
- Collection stays complete. Kiosk events are still captured and queryable — they're segmented into the bot lane, not dropped.
- You don't pay for it. Kiosk pageviews are excluded from billable human pageviews.
- It's excluded from enrichment. The enrichment dispatch refuses bot-classified traffic, so no enrichment budget is spent profiling a television.
- People who touch the kiosk aren't lost. Identity signals captured on a shared device belong to the people who used it, and the identity pipeline handles those separately — it's the device profile that's automated.
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:
- Bot traffic is labeled, not deleted. Events from bot-classified visitors are collected and segmented, so a misclassification loses you nothing permanently — mark the visitor human and their future events land in the human lane.
- Corrections beat automation. The operator override outranks every automated verdict, including the cached ones.
- Corrections are accountable. Role-gated, reason-carrying, audit-logged, reversible, and self-expiring.
- Suspicion never hard-blocks by default. Behavioral flags feed a suspicious bucket; gating actions require operator opt-in, with the override as the escape hatch.
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.