Most fingerprinting libraries return one hash that shifts whenever the browser shifts. Splitting signals into hardware-bound and engine-bound categories produces a second, stable hash that survives browser switches, incognito mode, and cookie clearing on the same physical device.
The problem with a single hash
A user opens your free trial in Chrome, exhausts it, clears cookies, and returns in Firefox. If your fingerprinting library returns a single hash, that hash changed. Firefox renders audio differently, applies different canvas rasterisation, and reports different engine-level properties. The device is identical. The hash is not.
This is not a bug in those libraries. It is a natural consequence of what they measure: the browser environment. Browser-environment signals are useful for recognizing a returning visitor in the same browser. They are not stable enough to serve as device identity across different browser engines.
The fix is to stop treating all signals as equivalent and start asking which signals describe the hardware and which describe the browser engine.
What a fingerprint actually bundles
Every device fingerprinting library works by collecting a set of observable browser properties and hashing them together. The key question is what those properties actually represent.
Some properties are stable properties of the underlying physical machine: the GPU model, the set of installed fonts, the display geometry, and similar device-level traits. These will report the same value in Chrome, Safari, and Firefox on the same device. They are hardware-bound.
Other properties are stable properties of how a particular browser engine processes content: the exact floating-point output of an audio DSP, the precise pixel values produced by a canvas fill, how the JavaScript engine formats an error stack, how WebGL maps geometry to pixels. These values differ between browser engines even when the underlying hardware is identical. They are engine-bound.
A single-hash library fuses both categories together. The result is a hash that is specific to a browser-on-a-device combination, not a device. Change the browser, change the hash.
Signal categories at a glance
| Category | Examples | Stable across browsers on the same device? | Changes when... |
|---|---|---|---|
| Hardware-bound | GPU, installed fonts, display geometry, and other device-level traits | Yes | Hardware changes: new GPU, OS reinstall, external monitor connects |
| Engine-bound | Audio DSP output, canvas pixel values, JS engine fingerprint, WebGL rendering | No | Browser engine changes: Chrome to Firefox, version upgrade, browser settings change |
Two outputs from one collection pass
Benny's answer to this problem is to tag every signal with its binding during collection and derive two separate hashes from a single pipeline run. You do not pay the collection cost twice.
The `fingerprint` field is computed from the full signal set. It is specific to a browser instance on a device and will differ between Chrome and Safari on the same machine. It is the right identifier for recognizing a returning visitor in the same browser, distinguishing two users who share a device, or flagging that someone's browser environment changed unexpectedly.
The `hardwareFingerprint` field is computed only from the hardware-bound signal subset. It is stable across Chrome, Safari, Firefox, and Brave on the same physical device. It is the right identifier for cross-browser device matching, anti-fraud trials, license enforcement, and anywhere the question is 'same device' rather than 'same browser session'.
Both are 16-character hex strings. One call to `getFingerprint` returns both.
import { getFingerprint } from 'doorman-benny';
const result = await getFingerprint();
// Per-browser hash: differs between Chrome and Safari on the same machine.
console.log(result.fingerprint);
// Hardware hash: identical in Chrome, Safari, Firefox, Brave on the same device.
console.log(result.hardwareFingerprint);
// Cross-browser score tells you how confidently the hardware hash was derived.
console.log(result.crossBrowser.stableSignals); // signals that contributed
console.log(result.crossBrowser.unstableSignals); // signals excluded due to noiseOne call, two hashes. Use fingerprint for browser-session identity and hardwareFingerprint for device identity.
When to use each hash
The two hashes answer different questions. Mixing them up produces either false positives (blocking a real user who switched browsers) or false negatives (failing to catch a fraudster who opened a new browser).
Use `fingerprint` when the question is browser-session identity: recognizing a returning visitor, flagging an unexpected browser upgrade, or detecting that a session token is being replayed from a different browser instance.
Use `hardwareFingerprint` when the question is device identity: preventing duplicate free-trial signups, detecting multi-accounting, binding a device to an MFA enrollment, or enforcing a per-device software license. The hardware hash is what survives cookie clears, browser switches, and incognito mode.
Choosing the right hash by use case
| Use case | Hash to use | Why |
|---|---|---|
| Recognize returning visitor, same browser | fingerprint | Browser-session identity is exactly what this hash represents |
| Block free-trial re-signup after browser switch | hardwareFingerprint | The trial belongs to the device, not the browser |
| Detect duplicate accounts / multi-accounting | hardwareFingerprint or compareFingerprints with cross-browser mode | Abusers switch browsers; the device stays the same |
| Detect unexpected session context change | fingerprint | Engine-bound signals will differ if the environment changed |
| MFA device binding | hardwareFingerprint | A bound device should match regardless of which browser is open |
| Anti-spoof / consistency check | result.consistency.spoofLikelihood | Independent of either hash; see scoring-consistency docs |
Why this is a structural difference, not an accuracy tweak
Libraries that return a single hash can improve that hash with more signals, noise reduction, or server-side stabilisation. But they cannot solve the cross-browser stability problem without tagging signals by binding and producing a separate hardware hash. More signals fused together still produce a different hash when the browser engine changes.
The hardware-vs-engine taxonomy is an architectural decision, not a configuration option. It determines what question the fingerprint can answer. A per-browser hash and a per-device hash are different things, and no amount of tuning converts one into the other.
For a practical walkthrough of the comparison API that puts both hashes to work, see the docs page for `compareFingerprints`. For an honest head-to-head with ThumbmarkJS, including where ThumbmarkJS is ahead, see the comparison page.
Frequently asked questions
Will hardwareFingerprint change if the user installs a new browser?
No. Installing a new browser does not change the underlying hardware. The hardware-bound signals (GPU identity, installed fonts, CPU architecture, and related properties) remain the same. The hardwareFingerprint should be identical whether the user opens Chrome, Safari, Firefox, or a newly installed browser on the same device.
Does the hardware hash change if the user reinstalls their OS?
It may change partially, depending on which signals shift during reinstallation. An OS reinstall can change the installed font set, reset hardware-level identifiers, or alter GPU driver reporting. In practice most of the hardware-bound signals survive a reinstall, but the hash may drift. compareFingerprints with the lenient or cross-browser mode can handle partial drift without a hard mismatch.
What happens in incognito mode?
The hardwareFingerprint is stable in incognito mode. Incognito mode prevents cookies and storage from persisting, but it does not change the hardware or alter how hardware-bound signals are reported. Engine-bound signals may show minor differences in some browsers, so the fingerprint (per-browser hash) can drift slightly in incognito, but the hardwareFingerprint is designed to be stable there.
Is there a performance penalty for collecting both hashes?
No. Both hashes are derived in a single collection pass. getFingerprint collects all signals in parallel and produces fingerprint and hardwareFingerprint in one pipeline run. If you only need the hardware hash and want faster collection, use getDeviceId instead, which skips engine-bound collectors and runs in roughly 200 to 300 ms.
How does this compare to what ThumbmarkJS returns?
ThumbmarkJS returns a single hash from its signal set. It does not tag signals by binding or produce a separate cross-browser hardware hash. For use cases that only need single-browser recognition, both approaches work. For cross-browser device matching, only the two-hash architecture solves the problem.
Get started
Ship cross-browser device identity in minutes
One npm install. Zero dependencies. Both hashes from a single call. The hardware-vs-engine split ships in the free tier.
Last reviewed June 9, 2026

