A user agent string takes seconds to fake. Getting every detail of a browser right at the same time is far harder. That asymmetry is the reason fingerprinting has become a workhorse of bot detection: instead of trusting what a client claims to be, you look at what it actually exposes.
Fingerprinting means collecting many small, mostly passive attributes — HTTP headers, TLS handshake details, JavaScript-visible browser properties, rendering output — and combining them into a profile. The aim isn't to identify a person. It's to answer two narrower questions: does this client look like a real browser on real hardware, and have we seen this exact profile before, in numbers that make sense?
No single attribute is decisive. A screen resolution proves nothing. A canvas hash proves very little. Stacked together, though, they narrow quickly, because real browsers produce correlated values: a specific engine, on a specific platform, at a specific version, renders and reports things in a consistent way.
Most detection setups draw from four groups:
Each item is weak and noisy on its own. The value comes from combination and consistency.
Bots rarely fail because one value is unusual. They fail because the values contradict each other. A browser claiming to be Chrome on Windows 11 while reporting a WebGL renderer of SwiftShader or Mesa is telling two stories at once. So is a desktop user agent paired with an 800x600 viewport, no device pixel ratio scaling, and language settings that disagree with both the IP location and the reported timezone.
These checks are worth running first, because they produce few false positives:
Then there are statistical oddities. Requests arriving at metronomic intervals. A form filled in under a second with no keystroke events. No mouse movement at all across a long session. Rendering hashes that reproduce exactly, session after session, when real hardware tends to vary slightly.
A fingerprint seen once tells you almost nothing. The same fingerprint appearing hundreds of times an hour across a dozen networks tells you a great deal. This is where fingerprinting earns its keep — not as a verdict on a single request, but as a way to link sessions and score their rate and reputation.
The practical move is to store the profile as a key, then track how many sessions use it, from how many distinct networks, over what period, with what behavioural similarity. A cluster of near-identical profiles using shuffled user agents is a strong indication of scripted traffic. A cluster that is genuinely identical but spread across one company's address range during office hours is probably a managed fleet — legitimate, and worth excluding before you tune thresholds.
Some of the most durable signals sit below JavaScript. The TLS ClientHello — cipher suite order, extensions, elliptic curves — is hard to fake from inside a page and cheap to compute at the edge. The same goes for HTTP/2 frame settings and header order. Libraries that impersonate a browser at the HTTP level often connect with a default TLS stack, which makes the mismatch obvious before a single byte of HTML is parsed.
Pairing these with in-page signals gives you two independent views of the same client. When they agree, confidence rises. When they disagree, you have something worth investigating.
Treat it as evidence, not proof. The common failure modes are predictable:
If you start small, start here: check a handful of hard contradictions between the user agent, the platform and the renderer; record the TLS and header-order fingerprint at the edge; and count how often each profile appears. Those three things catch a large share of crude automation without penalising anyone who simply uses an unusual browser.
Then add behaviour, because fingerprinting describes a client while behaviour describes an intent — and intent is what you actually care about.
Photo: cottonbro studio / Pexels