Published · Updated
How Anti-Bot Detection Really Works: Fingerprinting, TLS, and Behavior
Most advice on avoiding blocks reads like a checklist: rotate your IPs, set a real User-Agent, add delays. That's useful, but it treats detection as a series of independent checks to satisfy one by one. Modern anti-bot systems, like the ones protecting large e-commerce, travel, and search platforms, score traffic across three separate layers and combine the results. Understanding those layers explains why a "correct-looking" request still gets blocked, and why no single fix reliably solves it.
Layer 1: Network-level fingerprinting
Before your request payload is even parsed, the connection itself carries a fingerprint.
TLS fingerprinting (JA3/JA4). When a client opens a TLS connection, the order and content of its Client Hello (supported cipher suites, extensions, elliptic curves) varies by TLS library, not by what you set in your headers. Python's requests (via OpenSSL), Node's fetch, and a real Chrome browser each produce a distinct, stable fingerprint. Detection systems maintain databases mapping these fingerprints to known HTTP clients and libraries, so a request claiming to be Chrome via its User-Agent header while presenting Python's TLS fingerprint is an immediate, high-confidence signal, regardless of what's in the request body.
HTTP/2 fingerprinting. Real browsers negotiate HTTP/2 with a specific frame ordering, stream priority scheme, and settings frame values. Most scripting HTTP clients either don't support HTTP/2 at all or implement it differently enough to stand out. It's a second, independent signal, and you can only pass it by emulating a real browser's network stack along with its headers.
IP reputation. Separately, the IP itself carries history: whether it's a known datacenter range, how much traffic it's sent recently, and whether it's been flagged by other sites using the same detection vendor (shared threat intelligence across customers is common). This is the layer our guide to rotating proxies addresses, but it's only one input.
Layer 2: Browser environment fingerprinting
If a request executes JavaScript (and most serious detection now requires it, specifically to enable this layer), the page can probe the runtime itself:
- Automation flags.
navigator.webdriveristrueby default in unpatched Selenium, Puppeteer, and Playwright sessions. It's the single easiest tell, and the first thing any competent detection script checks. - Canvas and WebGL fingerprinting. Rendering a hidden canvas or WebGL scene produces pixel-level output that varies subtly by GPU, driver, and rendering stack. Headless Chrome's software rendering path often produces a fingerprint distinguishable from real hardware, even with the User-Agent spoofed.
- Inconsistency checks. Real browsers have internally consistent environments: screen resolution matches available fonts roughly matches plugin list roughly matches hardware concurrency. Spoofing one property (say, User-Agent claiming a phone) while leaving others unchanged (desktop screen dimensions, desktop-only fonts) creates a mismatch that's easy to flag algorithmically.
- CDP-specific behavior. Tools that drive a browser via the Chrome DevTools Protocol leave runtime traces (timing artifacts, extra properties on
window, specific error stack behavior) that differ from a human clicking through the same browser.
This is why our static vs. dynamic scraping guide matters even before detection comes up: running a full browser introduces this entire fingerprinting surface, which a plain HTTP request never exposes in the first place.
Layer 3: Behavioral analysis
The final layer looks at a whole session over time: mouse movement entropy (real cursors accelerate and decelerate non-linearly; scripted movement is often too straight or too smooth), the timing distribution between actions (constant intervals are as suspicious as inhumanly fast ones), scroll behavior, and how a session's request pattern compares to typical navigation paths through the site. This layer is why identical requests from the same setup can pass on one visit and fail on another: behavioral scores accumulate across a session and sometimes across a longer IP or fingerprint history.
Detection is a score
Because of these three layers, detection is almost never a single rule you can identify and defeat. It works more like a weighted risk score: a slightly off TLS fingerprint might be tolerated if the IP has a clean history and the behavior looks normal; a perfect browser fingerprint won't save a session sending requests every 200ms. This is also why two scrapers with seemingly identical setups get different outcomes: the score is cumulative across signals you may not be controlling at all.
It's also why fixing the tactic that got you blocked last time often doesn't work the next time. If you patched navigator.webdriver and started passing, a routine vendor update that checks a different signal puts you back where you started. Vendors like Akamai, Cloudflare, and DataDome update these checks continuously and share threat intelligence across customers, so chasing individual signals doesn't hold up over time. Our CAPTCHA guide treats a CAPTCHA challenge as an escalation of this same scoring system for the same reason.
What this means practically
Reducing your risk score usually means closing gaps in every layer instead of perfecting one:
- Use an HTTP client whose TLS and HTTP/2 fingerprint plausibly matches the User-Agent you're sending, rather than mixing a spoofed header with a mismatched network stack.
- Reserve full browser automation for pages that actually need it, since every additional layer of the environment is a new fingerprinting surface; see the headless browser decision guide.
- If you do run a browser, patch the well-known automation tells (
navigator.webdriverand friends) rather than assuming a stock Puppeteer/Playwright instance is invisible by default. - Pair clean fingerprints with clean IP reputation (see our rotating proxies guide), since a good fingerprint on a burned datacenter IP still scores poorly.
- For anything long-running, behavioral realism (timing, navigation order) matters as much as the first request.
Maintaining access to a changing source can become its own ongoing project. A managed data API puts that work behind a documented interface. PrismCrawl serves enterprise and production workloads across search engines, shopping, maps, travel, and reviews, with automatic CAPTCHA solving and JavaScript challenge handling. Customers receive API responses without seeing or completing challenges.
Frequently asked questions
Is anti-bot detection just checking the User-Agent header?
No, not anymore. Modern systems combine network-level fingerprinting (TLS handshake, HTTP/2 frame ordering), browser environment checks (automation flags, canvas/WebGL rendering, font enumeration), and behavioral analysis (mouse movement, timing, session history) into a single risk score. Headers are one signal among many.
Can a headless browser pass as a real user?
By default, no. Tools like Puppeteer and Playwright leave detectable traces (navigator.webdriver, missing plugins, CDP-specific runtime behavior) unless explicitly patched. Stealth plugins close some gaps, but detection systems update faster than most teams can track manually.
Why does the same request get blocked sometimes and not others?
Because detection is usually a cumulative risk score, not a single pass/fail rule. An identical request can look fine from a fresh residential IP with a clean session history, and get blocked from a datacenter IP with a flagged fingerprint or an unusual request pattern, even if every header is identical.