FingerprintOptions
Every public function accepts an optional FingerprintOptions object. All fields have safe defaults, so you can start with zero configuration and tune as needed.
Options reference
| Field | Type | Default | Description |
|---|---|---|---|
| tiers | number[] | [1] | Which tier levels to collect. Tier 1 is fast and reliable (17 signals); tier 2 adds broader coverage (7 signals); tier 3 is niche and low-entropy (2 signals). |
| timeout | number | 5000 | Global collection timeout in milliseconds. Collection stops after this deadline; any unfinished signal is recorded as absent. |
| exclude | string[] | [] | Signal names to skip entirely. Useful for removing signals that are slow or redundant for your use case. |
| debug | boolean | false | When true, raw signal values (before hashing) are included in the output. Keep false in production. |
| componentTimeout | number | undefined | Optional per-collector timeout in milliseconds. When set, every individual signal collector is independently capped at this many ms; whichever fires first (this per-component timer or the global timeout) kills the collector. Killed collectors report sentinel: 'timeout'. Non-positive or non-finite values fall back to global-only. |
| stabilize | ("private" | "iframe")[] | undefined | Optional opt-in stability mode. When set, signals known to be farbled or noised by the detected browser are dropped before collection, so the fingerprint stays stable across calls on those browsers in exchange for entropy on that platform. The 'private' rule set drops the signals each privacy-hardened browser is known to randomise, primarily canvas, audio, and speech_voices on browsers like Brave, Firefox, Safari 17+, and Samsung Internet. Excluded signals appear with confidence: 'stabilized'. |
| warmup | boolean | false | When true, yields once via requestIdleCallback (with setTimeout(0) fallback, bounded at 50 ms) before any collectors fire. Lets the page settle after first paint so timing-sensitive signals like audio and webgl_gpu_trace sample on a quieter main thread. No hash change, verified by unit test. Opt-in because the 50 ms wait is undesirable on latency-critical flows. |
| performance | boolean | false | When true, attaches a _pipeline timing block to the result. Breaks down collect, crossBrowser, checks, fuse, and total in milliseconds. Useful for profiling; keep false in production. |
| bindings | ("hardware" | "engine")[] | undefined | When set, restricts collection to collectors whose binding matches. getDeviceId uses bindings: ['hardware'] internally to skip all engine collectors. Passing bindings: ['engine'] is rarely needed but valid. |
The tier model
Signals are grouped into tiers based on reliability and collection cost. Tier 1 runs the default signal set: fast, high-entropy signals that work well across all supported browsers. The specific signals in each tier are catalogued on the overview page.
Tier 2 adds seven opt-in signals: css_probe, media_devices, webgl_gpu_trace, webgpu, webgl_pixel_readback, permissions, and webrtc_sdp. These are slightly slower or have more variance across browsers. permissions and webrtc_sdp were both added post-1.20.0. Tier 3 is two niche signals (speech_voices and storage_quota) that are low-entropy and restricted in some environments.
Pass tiers: [1, 2] or tiers: [1, 2, 3] to opt in to broader coverage. The default tiers: [1] is the right choice for most deployments.
Timeout semantics
The timeout field is a global deadline for the entire collection pass. When the deadline fires, any signal that has not yet completed is recorded as absent (confidence: 'absent', sentinel: 'timeout') rather than causing a failure. This means you always get a result; it may just have fewer signals.
Individual signal collectors also have internal safety-net timeouts that prevent a single slow API (such as a throttled AudioContext) from blocking all others. These internal timeouts are not user-configurable; the global timeout field controls the outer boundary.
Per-component timeout vs global timeout
When `componentTimeout` is set, every collector also gets its own independent timer in addition to the global `timeout` deadline. Whichever fires first wins. A hung collector killed by the per-component timer is recorded as absent with sentinel: 'timeout' and a message of "… exceeded per-component timeout (<n>ms)". A collector killed by the global timer reports "… did not complete within global timeout (<n>ms)". The two paths are distinguishable in `result.errors`.
Hash invariance is guaranteed: the fingerprint hash is byte-identical whether a hung collector is killed by the global timer or by a per-component cap.
Post-processing helpers (auto-applied, not options)
A post-processing pipeline runs automatically inside getFingerprint and getDeviceId after the consistency checks complete. It is not configurable; it is always applied in this order:
1. Known-browser uniform-noise suppression: strips canvas_noise_uniform and audio_noise_uniform from consistency.flags when the UA matches a browser that produces algorithmically uniform noise legitimately (Safari 17+, Samsung Internet). Score and spoofLikelihood are recomputed against the corrected flag list.
2. Brave-aware spoofLikelihood suppression: when brave_shields_active is in consistency.flags, the spoofLikelihood bucket is re-computed against the non-Brave flag count so that a real Brave user with no other evidence reads 'low'. The brave_shields_active flag remains visible in flags[].
import { getFingerprint } from 'doorman-benny'; // Tier 1 only, default timeout (recommended for most cases).
const result = await getFingerprint(); // Tier 1 + 2 for hardware fingerprinting with media_devices and webrtc_sdp.
const result2 = await getFingerprint({ tiers: [1, 2] }); // Shorter timeout for latency-sensitive flows.
const result3 = await getFingerprint({ timeout: 2000 }); // Skip a specific signal.
const result4 = await getFingerprint({ exclude: ['fonts'] }); // Per-collector cap: kill any single collector that takes >800 ms.
const result5 = await getFingerprint({ timeout: 5000, componentTimeout: 800 }); // Stable hash on Brave, Firefox-resist, Safari 17+, and Samsung Internet.
const result6 = await getFingerprint({ stabilize: ['private'] }); // Warmup yield: let first paint settle before sampling timing-sensitive signals.
const result7 = await getFingerprint({ warmup: true }); // Pipeline timing: for profiling only, never in production.
const result8 = await getFingerprint({ performance: true });
console.log(result8._pipeline?.total); // total ms // Hardware signals only (same as getDeviceId internally).
const result9 = await getFingerprint({ bindings: ['hardware'] }); // Debug mode: raw values included (never use in production).
const result10 = await getFingerprint({ debug: true });All options pass through to createCollector and getDeviceId as well.
Last reviewed 2026-06-04

