Bot Detection: A Developer's Guide to Identifying and Blocking Automated Traffic
How to block bad bots in production — signals that work, techniques with honest tradeoffs, and code you can ship.
Every production web app absorbs a steady stream of automated traffic, and distinguishing the useful bots (search crawlers, uptime monitors) from the hostile ones (scrapers, credential stuffers, fake-signup farms) is what bot detection is for. It's more than a user-agent check or a CAPTCHA prompt — bot detection is the process of identifying non-human traffic in real time from the signals a client emits, scored against request context and history. This article covers the signals that work, the techniques that combine them, a minimal implementation you can extend, and the failure modes vendor overviews skip.
The scale justifies the work. According to Imperva's 2025 Bad Bot Report, 51% of all internet traffic in 2024 was automated, and roughly 72% of that was bad — scraping, credential stuffing, scalping, account takeover. If your app does anything with money, content, or user accounts, some share of your traffic is already hostile.
How Bot Detection Actually Works
Bot detection is the real-time classification of incoming traffic as human or automated, based on dozens of weak signals combined into a single score. No individual signal is decisive. The classifier is what does the work.
The useful mental model is the same one underneath spam detection. No single feature — not the sender's domain, not a specific word, not the link count — tells you a message is spam. You combine many weak signals into a score and pick a threshold. Bot detection works the same way. A missing JavaScript execution isn't automation by itself; neither is a data-center IP or a stable fingerprint. Put three of them together on the same request and the probability shifts.
The rest of this article walks through four signal families (client-side, behavioural, network, challenge-response), the techniques that combine them, and what to do when the signals disagree.
The Signals That Matter (And How Reliable Each One Is)
Every bot detection article lists the same signal types and presents them as equivalent. They aren't. Some are trivially spoofed; others hold up against well-resourced attackers. A developer-friendly assessment matters because architecture follows reliability — you don't want to make a blocking decision on a signal an attacker flips with a single flag.
Browser and device fingerprinting
A fingerprint is a stable identifier derived from how the client's browser renders and responds — canvas output, WebGL parameters, installed fonts, screen metrics, audio stack, timing quirks. It survives cookie clears and private browsing because it's computed from the environment, not stored on it. The Electronic Frontier Foundation's Cover Your Tracks project (previously Panopticlick) has spent more than a decade documenting how much entropy a standard browser exposes.
Fingerprinting is a cheap, durable bot signal. Because it's computed in the browser, the signal is independent of the request's network path — a fingerprint-based identity survives an attacker rotating IPs because the environment keeps producing the same value. And collection runs in under 100ms client-side with no UX cost.
Fingerprints also separate automated browsers from normal ones. Automation frameworks leak detectable artefacts in canvas output, navigator properties, and permissions APIs, so headless Chrome and real Chrome produce noticeably different fingerprints. That difference is a signal, not a verdict — classifying a visit as automated still requires comparing against a baseline of expected values downstream of the fingerprint itself.
The foundational browser fingerprinting explainer covers how the signals compose; the browser fingerprinting techniques deep-dive covers which components contribute most entropy.
Behavioural signals
Mouse movement, typing cadence, scroll velocity, and focus events. Real users produce noisy, non-linear patterns; naive bots produce straight lines or no interaction at all. Sophisticated bots replay recorded human traces.
Behavioural signals are strongest on interactive surfaces (checkouts, signup forms) and nearly useless for endpoint-only attacks (API scraping, credential spray against a login POST). They're also latency-sensitive — you need enough observation window to classify, which trades off against user experience.
Network-level signals
IP reputation, Autonomous System Number (ASN), request rate, and TLS fingerprint. Data-center ASNs and known residential-proxy ranges are strong negative signals; household IP addresses on consumer ISPs are weak because attackers buy them by the thousand through residential proxy networks.
Network signals alone are not enough anymore. Residential proxies are cheap, and serious bot operators rotate through tens of thousands of consumer IPs per campaign. Treat the network layer as context that modifies other signals, not as a decision input on its own.
Challenge-response
CAPTCHA prompts, proof-of-work puzzles, invisible browser challenges (silent JavaScript checks that test for headless features). Challenges are unambiguous — a passed challenge is evidence, a failed one is action — but they carry real cost. Visible CAPTCHAs add friction and fail accessibility audits; invisible challenges can break privacy-respecting browsers.
Reserve challenges for moments of elevated risk signalled by the other three categories, rather than running them on every request.
Headless browser detection
A specific sub-category worth calling out: automation frameworks like Puppeteer, Playwright, and Selenium expose detectable properties (the navigator.webdriver flag, unusual plugin lists, missing permissions APIs, modified user-agent strings). Modern attackers patch these before shipping, so headless detection catches the long tail of unsophisticated bots and buys you nothing against serious ones. Include it because it's cheap, not because it's load-bearing.
How the signals rank
Not every signal pulls its weight. The following table summarises the tradeoff — reliability against common attackers, resistance to spoofing, and UX cost.
Signal | Reliability | Spoofability | UX cost |
|---|---|---|---|
Browser fingerprint | High | Medium (requires fingerprint rotation infrastructure) | Zero |
Behavioural | High on interactive pages | High with replay attacks | Zero (passive) |
Network (IP, ASN) | Medium | Low against residential proxies | Zero |
Challenge-response | Definitive when passed | Low to medium | High (visible) or medium (invisible) |
Headless detection | Low | Trivial for motivated attackers | Zero |
Bot Detection Techniques and Their Tradeoffs
Techniques combine signals into decisions. The three you'll see in practice:
Static rule-based detection. Hand-coded rules: block all traffic from a specific ASN, rate-limit by IP, fail any request missing a required JavaScript challenge. Easy to implement, easy to reason about, easy to bypass. Rules age badly — every maintainer inherits a pile of stale blocks they're afraid to remove. Useful as a fast-path layer in front of something smarter.
Behavioural and ML-based detection. Train a classifier on historical traffic labelled as human or bot, then score new requests. Strong in theory, dependent on labelled data in practice. If you don't already have a ground-truth source (existing CAPTCHA outcomes, known-bad sessions flagged by downstream fraud systems), your model will drift. Production ML-based detection needs a retraining loop, which is why the teams running it well treat it as ongoing infrastructure, not a one-time ship.
Client-side attestation with server-side scoring. Collect fingerprints and challenge responses in the browser, send them to the server, combine with request context, and score. This is the architecture most modern fingerprint-based systems use, including the implementation in the next section. It scales, it degrades gracefully, and it moves the final decision server-side where attackers can't see it.
Most production systems are hybrids. A typical stack uses static rules as a WAF-layer first pass, a fingerprinting layer for identity, and a scoring service that fuses the two. The question isn't which technique to pick — it's how to sequence them so each layer handles what the one before it can't.
How to Detect Bots in Practice — A Minimal Implementation
This section shows the smallest functional pipeline that captures the shape of a real system. It's not production code — it's the seed you extend. The pipeline has four steps: collect a fingerprint client-side, post it to the server with request context, score it, and return a decision.
Step 1: Collect a fingerprint on the client
The ThumbmarkJS open-source library runs in the browser, computes a stable fingerprint, and returns a visitor identifier. Integrating it is a single import.
// Collect a browser fingerprint and send it to the detection endpoint
import { getFingerprint } from '@thumbmarkjs/thumbmarkjs';
async function reportVisitor() {
const fingerprint = await getFingerprint();
await fetch('/api/bot-check', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
visitorId: fingerprint.thumbmark,
components: fingerprint.components,
referrer: document.referrer,
pathname: window.location.pathname,
}),
});
}
reportVisitor();The fingerprint object contains a stable thumbmark hash and the raw components it was computed from, which lets the server do its own inspection rather than trusting a single hash.
Step 2: Score on the server
The server combines the fingerprint with request-level context (source IP, ASN lookup, request rate) and emits a decision. The rules below are intentionally simple — replace them with whatever fits your threat model.
// Score a visitor against fingerprint, network, and rate signals
async function scoreVisitor(req) {
const { visitorId, components } = req.body;
const ip = req.headers['x-forwarded-for'] || req.socket.remoteAddress;
const asn = await lookupAsn(ip);
const fingerprintReuse = await countRecentVisitors(visitorId, '1h');
const isHeadless = components.userAgent?.match(/HeadlessChrome|PhantomJS/);
let score = 0;
if (asn.isDataCenter) score += 40;
if (fingerprintReuse > 50) score += 30; // same fingerprint, 50+ sessions/hour
if (isHeadless) score += 50;
if (!components.canvas || !components.webgl) score += 20; // incomplete fingerprint
if (score >= 70) return { decision: 'block', score };
if (score >= 40) return { decision: 'challenge', score };
return { decision: 'allow', score };
}The challenge decision is the important middle state. On an ambiguous score, you serve a CAPTCHA or an invisible proof-of-work, which moves the traffic without blocking legitimate users. Blocks should be rare; most real pipelines are dominated by allow-with-logging and challenge decisions.
The scoring weights above are illustrative. Real weights come from tuning against your own traffic over weeks, not from a reference article.
Step 3: Log everything
Log every decision, the inputs that drove it, and the score. You will need the history to tune weights, investigate false positives, and build the ground-truth labels that a future ML model will train on. Teams that skip this step end up guessing at why detection works or fails a quarter later.
Bot Detection Tools vs. Building In-House
The bot detection software market is crowded with vendor products that promise similar things. That doesn't mean buying is always right — the decision turns on four questions.
How broad is your attack surface? A single scraping-prone endpoint is a build problem. A dozen surfaces (signup, login, search, checkout, comments, API) with different threat profiles is a buy problem.
How sophisticated is the attacker? Casual scrapers are blocked by open-source fingerprinting and rate limits. State-of-the-art credential-stuffing operators using residential proxies, CAPTCHA-solving services, and fingerprint rotation need a vendor with dedicated research capacity.
What's your security engineering capacity? Bot detection is not ship-and-forget. Tuning, retraining, and adversarial response eat real engineering time. If you can't dedicate a person, buy.
What's the cost of a mistake? A blocked legitimate customer on a $5 SaaS signup is annoying; one on a $5,000 B2B checkout is a lost deal. High stakes justify a managed product with tighter false-positive controls.
Most teams run hybrid stacks: an open-source fingerprinting library for the core identity signal, a commercial bot management product for the hardest traffic, and a set of in-house rules for application-specific abuse. This is why evaluating bot detection tools is worth doing on your actual traffic, not against a vendor's case studies.
Common Bot Detection Failure Modes
Every production bot detection system produces false positives — legitimate users who get blocked or challenged. Planning for them is the difference between a system that earns trust and one that fills the support queue. The four patterns below cover most of what you'll see.
Privacy-hardened browsers. Brave, Tor Browser, Firefox with strict resistFingerprinting, and hardened Safari configurations all deliberately reduce fingerprint entropy to make tracking harder. That same reduction makes their fingerprints look automated. If your detection penalises incomplete fingerprints (as the example above does), you will block privacy-conscious users by default. Mitigation: weight incomplete fingerprints lower, and pair them with behavioural signals before challenging.
Accessibility tools and legitimate automation. Screen readers, keyboard-only navigation, and browser extensions that automate form filling all look unusual to behavioural models. So do first-party automated tests hitting a staging environment. Mitigation: allowlist your own infrastructure explicitly, and treat the assistive-technology tail by widening tolerances rather than tightening them.
Adversarial adaptation. Once attackers know your detection logic, they rotate fingerprints, randomise timing, and mimic the traffic shapes you treat as human. Public documentation of your approach (this article included, for a vendor) accelerates this. Mitigation: treat detection as a moving target, rotate weights regularly, and invest more in hard-to-spoof signals (cross-session fingerprint consistency, behavioural entropy at scale) than in easy-to-discover rules.
Fingerprint drift across browser updates. Browser vendors change font rendering, canvas output, and WebGL parameters every few releases, which shifts fingerprint values for real users. If your model treats "fingerprint change" as hostile, a Chrome auto-update looks like an attack. Mitigation: version-aware fingerprint comparison and decay old reference values on a known release cadence.
The common theme: bot detection is a signal-aggregation problem with a long tail of real users whose signals resemble bots. Ship conservatively, challenge before blocking, and keep a path for a miscategorised user to prove they're human without opening a support ticket.
Bot Detection with ThumbmarkJS
The implementation above uses the ThumbmarkJS open-source library as the client-side fingerprint collection layer — the same library, unmodified, that runs in production bot-detection pipelines today. It's MIT-licensed and stable to pin.
Teams that want the server-side half managed use ThumbmarkJS for identity resolution, fingerprint reuse detection, and scoring. The free and paid tiers expose the same features and behave identically; the difference is quota and rate limits, with the free tier rate-limited tightly enough for evaluation and low-volume use and the paid tier sized for production traffic. Because the library and the API share the same fingerprint format, the components payload from the client-side example above is directly consumable server-side without rewriting your collection layer.
Conclusion
Bot detection is a signal-aggregation problem solved well by the teams that treat it as ongoing classifier tuning, not a one-time install. Start with a fingerprint, layer network context and behavioural signals on top, score server-side, and log every decision so you can learn from the misses. The failure modes matter as much as the detection logic — the fastest way to lose trust is to block a real customer who hits your privacy-hardened browser edge case.
If you want to start experimenting, the ThumbmarkJS open-source library is the simplest client-side entry point and ships with the fingerprint format used in the example above. Teams that want a managed scoring layer on top can pair it with the ThumbmarkJS API and explore the other ThumbmarkJS use cases — new account fraud prevention, content scraping, credential stuffing — that reuse the same identity layer.