How canvas fingerprinting works
The script creates an invisible <canvas> element, draws shapes, gradients, and text on it using canvas.getContext("2d"), then calls canvas.toDataURL() to read the pixel data back. The pixel output depends on: the GPU manufacturer and model, the driver version, OS-level font rendering (Windows ClearType vs macOS CoreText), and canvas DPI scaling. Hashing the result produces a short identifier that is stable for the same device across sessions and different for almost every other device.
WebGL fingerprinting works the same way but for 3D rendering, and additionally exposes the GPU name via gl.getParameter(gl.RENDERER) — real Chrome returns something like ANGLE (Intel, Intel(R) UHD Graphics 620 Direct3D11 vs_5_0 ps_5_0), while headless Chrome with no GPU returns a generic software string or crashes the WebGL context entirely.
Why scrapers get caught
Headless browsers do not have a GPU. They fall back to software rendering — either Chrome's built-in SwiftShader or Mesa llvmpipe on Linux. Both produce canvas hashes from a deterministic, known set. Anti-bots maintain a catalogue of these hashes and flag any session that produces one. Even worse: if the script asks for the WebGL renderer string and gets SwiftShader Device 0x0000C0DE, that is on a blocklist directly.
Patching toDataURL() in JavaScript does not help. Function.prototype.toString.call(canvas.toDataURL) reveals the patched source instead of [native code], so the patch itself becomes the bot signal.
How real bypasses work
Real fixes happen at the C++ level. CloakBrowser patches Chromium's rendering layer to inject slight per-profile noise into pixel values before toDataURL() returns — different hash every profile, identical visually. Camoufox does the same for Firefox via build-time patches. Real browsers running on consumer hardware are the gold standard: a real laptop with an Intel iGPU produces a real canvas hash that nothing in the blocklist matches.
A 2026 development complicates things further: services like Bablosoft PerfectCanvas now sell real harvested canvas hashes from real consumer GPUs that can be replayed into headless sessions. In response, anti-bots are pairing canvas probes with harder-to-replay signals (WASM SIMD timing, behavioural Bezier curve physics) so a "passing" canvas hash alone is no longer proof of a real browser.
PerfectCanvas — replaying a real machine's output
Naive canvas spoofing adds noise to the pixel output. This fails because anti-bot vendors expect stable output per-machine — randomising the canvas hash on every load looks more like a scraper than a fixed-but-wrong hash. The current state-of-the-art bypass is canvas replay, popularised by the PerfectCanvas project bundled into multilogin and several commercial anti-detect browsers.
The approach is: (1) on a real machine, generate canvas output for a wide set of known render operations and save the resulting hashes, (2) when the headless browser is asked to render any of those operations, return the pre-recorded pixels rather than the SwiftShader-degraded output. The result is byte-identical to the real machine for any test the anti-bot vendor knows to ask, and the consistent-per-session property is preserved.
The catch is novel render operations. Anti-bot vendors that suspect canvas replay can issue a render the database hasn't seen — the headless browser falls back to its real (broken) output, mismatch detected. The arms race here is whether the replay database has wider coverage than the vendor's test set.
