Quick verdict: Modern browser fingerprinting collects 30+ signals from your browser and network and combines them via entropy weighting to produce a near-unique identifier. The dominant signals are canvas hash, WebGL renderer, audio context, TLS ClientHello, and HTTP/2 settings — together they fingerprint you with 99%+ uniqueness across a 100K-user pool. Defeating fingerprinting requires consistent spoofing of ALL layers simultaneously; spoofing one (e.g., User-Agent) while leaving the others intact actually makes you MORE identifiable as a bot.
Anti-bot services and fraud-prevention tools collect signals at three layers:
| Layer | Signal source | Example signals |
|---|---|---|
| Network | TCP/TLS/HTTP handshake | JA3 hash, HTTP/2 SETTINGS frame, IP/ASN reputation |
| Browser environment | JavaScript queries | Canvas, WebGL, audio context, fonts, plugins, screen, timezone |
| Behavioral | User interaction patterns | Mouse path, scroll velocity, keystroke timing, click accuracy |
Signals from all three layers are combined to score the request. A high-entropy combination = high confidence in identification. Low entropy = "could be many users" but still useful as a session linker.
Renders a hidden image, hashes pixel data. ~99% uniqueness when combined with other signals. See Canvas fingerprinting deep-dive.
WebGLRenderingContext.getParameter(WebGLRenderingContext.RENDERER) returns a string like "ANGLE (NVIDIA, NVIDIA GeForce RTX 3080 (0x00002484) Direct3D11 vs_5_0 ps_5_0, D3D11)". Reveals: GPU model, driver, OS rendering backend. Very high entropy.
Generate an audio signal, run it through the Web Audio API's processing pipeline, sample the output. Different OS audio implementations + CPU floating-point behavior produce slightly different signals. ~95% uniqueness.
Enumerate installed system fonts by trying to render text in each and measuring width. Most users have ~50-200 fonts; the specific set is fairly unique. Privacy-focused browsers limit access to this; commercial browsers do not.
navigator.hardwareConcurrency — CPU core count (typically 4, 8, 12, 16)navigator.deviceMemory — RAM tier (0.25, 0.5, 1, 2, 4, 8 GB)navigator.maxTouchPoints — touch screen supportscreen.width/height/colorDepth/pixelRationavigator.plugins and navigator.mimeTypes used to be highly identifying (Flash, Java, PDF readers); now mostly empty in modern browsers. Their EMPTINESS itself is a signal — a browser with NO PDF plugin is unusual.
WebRTC reveals the local IP behind NAT and the public IP — even through a VPN if WebRTC is not blocked. Common privacy leak. Anti-bot services use this to detect VPN/proxy use.
Intl.DateTimeFormat().resolvedOptions().timeZone returns "America/Los_Angeles" or similar. navigator.language returns "en-US". A user with US English language but Asian timezone → suspicious.
A hash of the TLS ClientHello: cipher suite list, supported elliptic curves, extensions, signature algorithms. Python's requests and most non-browser HTTP clients produce a JA3 that is recognizably non-Chrome. Even spoofing the User-Agent does not change JA3.
This is why anti-bot services trivially distinguish Python scrapers from real browsers. Defeating it requires libraries that impersonate Chrome's TLS stack (curl_cffi, tls-client, undici with custom settings).
HTTP/2 connections start with a SETTINGS frame announcing client preferences (max concurrent streams, header table size, initial window size). The values and ORDER are part of the fingerprint. Chrome's order is distinctive; Python libraries use different defaults.
Browsers send HTTP headers in a specific order (Host, Connection, sec-ch-ua, sec-ch-ua-mobile, sec-ch-ua-platform, Upgrade-Insecure-Requests, User-Agent, Accept, ...). Curl and requests use different orders. Detectable.
The IP itself is a signal. AWS/GCP/Azure ASNs are flagged automatically. Mobile carrier ASNs are trusted. Residential ISP ASNs are middle. This is why residential proxies matter — they shift you from "obviously bot" to "ambiguous."
scrollTo with instant changes.Behavioral signals fire AFTER you arrive on the page, supplementing the network/JS fingerprint with "how does this user behave."
Each signal has an "entropy" — the information it contributes about which user you are. Approximate values:
| Signal | Entropy (bits) | Notes |
|---|---|---|
| User-Agent | ~7 | Coarse: OS family + browser version |
| Screen resolution | ~5 | ~30 common values |
| Timezone | ~3 | ~24 main values |
| Language list | ~6 | Plus dialect |
| Canvas hash | ~17 | Near-unique among ~100K users |
| WebGL renderer | ~12 | GPU + driver |
| Audio context | ~9 | OS audio stack |
| Font list | ~13 | 50-200 fonts varies |
| Plugins/MIME | ~5 | Mostly empty modern |
| JA3 TLS | ~12 | Browser+version |
| HTTP/2 settings | ~5 | Library detection |
Total: ~94 bits combined. To uniquely identify one user in a 100K population needs only ~17 bits. The fingerprinting system has 5x more entropy than it needs — even with several signals spoofed, you remain identifiable.
This is why partial spoofing is worse than no spoofing. Real browser-A has consistent signals across all 30 dimensions. Bot pretending to be browser-A has Chrome UA + Chrome canvas + Linux WebGL + Python TLS — the INCONSISTENCY itself is a flag.
For tough targets, you need ALL of the above. Spoofing the UA alone is worse than nothing.
Related: Canvas fingerprinting, Browser fingerprinting explained, Top antidetect browsers, Verify your fingerprint.