Overview
After collection completes, the result map is passed to a fusion stage that produces two hashes. fingerprint fuses the full collected signal set and is bound to a specific browser instance — it changes when the user switches engines because some signals reflect how the engine processes or renders, not what the underlying device is. hardwareFingerprint fuses a smaller subset of signals classified as hardware-bound and is designed to remain identical across browser switches and incognito sessions on the same physical device.
Both hashes are 16-character lowercase hex strings produced by the same hashing primitive used everywhere else in Benny (see concept-hashing).
Fusion composition is deterministic: a given environment produces a byte-identical pair of hashes on every call. The exact composition recipe — which signals contribute, in what order, and how a missing signal is encoded — is a deliberate part of Benny's defensive posture and is not part of the public contract.
// Public-facing fusion outputs on FingerprintResult
interface FingerprintResult {
fingerprint: string; // 16-char lowercase hex, per-browser-instance
hardwareFingerprint: string; // 16-char lowercase hex, cross-browser device
crossBrowser: CrossBrowserScore;
signals: Record<string, SignalResult>;
// ... consistency, incognito, automation, errors
}
interface CrossBrowserScore {
hardwareHash: string; // mirrored to result.hardwareFingerprint
confidence: number; // 0.0 to 1.0
stableSignals: string[]; // names that contributed normally
unstableSignals: string[]; // names that were absent or unstable
}Treat the two hash strings, the numeric crossBrowser.confidence, and the stable/unstable signal-name arrays as the stable interface. The internal set of signals fused into each hash, the exact confidence-score formula, and the sentinel encoding used for missing signals are not part of the public contract.
Two hashes, two purposes
fingerprint is intended for same-browser re-identification: detecting a returning visitor in the same Chrome on the same device, even across cleared cookies. Because it incorporates engine-bound signals, it changes when the user switches browsers — which is the correct behaviour for that use case.
hardwareFingerprint is intended for cross-browser device identity: linking a session in Chrome to a later session in Safari or Firefox on the same physical machine, or detecting a fraud actor switching browsers to evade per-browser bans. Because it deliberately excludes engine-bound signals, it is stable across browser switches but is necessarily less unique than the full fingerprint.
Both hashes are designed for backend correlation, not as standalone identity assertions. Production deployments should combine them with other context (account history, behavioural signals, network features) rather than treating either hash as a sole identity decision input.
Hardware vs engine binding
Every collector declares a binding of 'hardware' or 'engine'. The binding answers a single question: does this signal reflect a property of the physical device and operating system, or does it reflect how the browser engine processes or renders something?
Hardware-bound signals are the candidates for inclusion in hardwareFingerprint. Engine-bound signals never contribute to hardwareFingerprint — by definition, they would differ across browser switches on the same device and would defeat the cross-browser stability guarantee.
Classifying a new signal's binding is a consequential decision. Some signals that initially look hardware-bound turn out to be engine-bound on closer inspection: browser-imposed capability limits on GPU surfaces can vary across engines on identical hardware, and some browsers deterministically randomise display geometry per site as an anti-fingerprinting measure. The hardware list is kept deliberately conservative.
Binding model
| Binding | Definition | Eligible for hardwareFingerprint | Stable across browsers |
|---|---|---|---|
| 'hardware' | Reflects physical device or OS characteristics. Designed to be constant regardless of which browser engine is running. | Yes | Yes |
| 'engine' | Reflects how the browser engine processes or renders something. Differs across Chrome, Safari, Firefox on the same device. | No | No. Changes with browser switches or engine updates |
Missing signals and result completeness
Fusion composes over a fixed schema. A signal that timed out, threw, or is unsupported in the current environment contributes a typed absent placeholder rather than being silently dropped — so the slot count never varies at runtime and the composite hash changes correctly when a previously-present signal becomes absent.
The exact placeholder encoding is internal. Consumers should not depend on it and should not attempt to reconstruct the composite hash off-device.
If the fusion stage itself fails for any reason, Benny returns a safe-fallback result rather than throwing: fingerprint and hardwareFingerprint are set to deterministic fallback values and the error is surfaced on FingerprintResult.errors for diagnostics.
crossBrowser.confidence
Alongside hardwareFingerprint, fusion attaches a CrossBrowserScore that summarises how many of the hardware-bound signals contributed a normal, stable measurement on this call. A high confidence means the cross-browser identity is well supported by the available evidence; a low confidence means several hardware signals were absent or unstable and the hashed identity is correspondingly less reliable.
stableSignals and unstableSignals expose the per-signal breakdown as opaque string names. Consumers should forward these for telemetry rather than branching on individual names; the set evolves between releases.
The numeric crossBrowser.confidence (0.0 to 1.0) is the stable interface. Backend rules that gate decisions on cross-browser identity quality should key off that numeric score, not arithmetic over the stable/unstable lists.
Things worth knowing
- Both fingerprint and hardwareFingerprint are 16-character lowercase hex strings. See concept-hashing for the algorithm.
- getDeviceId restricts collection to hardware-bound signals only. Its returned id matches the hardwareFingerprint a full getFingerprint call would produce for the same environment with identical options, but it returns noticeably faster because the engine-bound signals are skipped.
- debug: true is the only way to include raw signal values in the FingerprintResult. The production default exposes only hash, confidence, binding, and timeMs per signal.
- The signals map in FingerprintResult contains only the collectors that passed the tier and exclusion filters at collection time. Signals excluded by configuration do not appear as absent entries; they are simply not present.
- Fusion fails safe: if the fusion stage throws, deterministic fallback hashes are returned and the error is surfaced on FingerprintResult.errors. A broken fusion run cannot crash a caller.
- The exact composition of each hash — which signals contribute, in what order, and how absent signals are encoded — is intentionally not part of the public contract and may change between releases.
Last reviewed 2026-06-04

