Fraud checks work best when they combine many small pieces of evidence. WhiskerJS adds one kind: a description of the browser and device, plus warnings when something looks automated or tampered with. Your server puts that next to what it already knows (the account, its history, the network) and decides. WhiskerJS never makes that decision for you.
The problem
Most fraud is repetition: the same person or tool coming back under a new name, a stolen password used from a strange machine, a script trying thousands of logins. Server-side data (IP address, request headers, account history) shows part of it. IP addresses rotate cheaply and headers are easy to set, so a description of the device itself is a useful extra view.
What WhiskerJS contributes
One call to collect() returns:
- 46 components, each with a status. 37 stable ones form the ids; nine change too easily and are reported only for your rules. See Signals.
- visitorId: an exact-match key for a browser setup.
- structuredId: seven parts, one per signal group (
gpu,audio,fonts,device,browser,locale,prefs), so you can measure how similar two devices are, not only whether they are equal. - integrityFlags: 14 read-only checks for automation and changed browser functions.
- binding (optional): your nonce and timestamp, bound to the result with a digest.
It does this without a network request, without storage and without a permission prompt. You send the result yourself with a request you already make, such as the login or checkout post. See Integration.
Signals and flags that matter
For fraud work the most useful pieces are usually:
| Piece | Why it matters |
|---|---|
time_zone, languages, intl_locale |
compare with the IP address region and the Accept-Language header your server sees |
ua_client_hints, platform |
compare with the User-Agent header: a Windows platform with a Mac user agent is worth a look |
webgl_renderer, canvas |
graphics differ between real devices; a software renderer is common on servers |
browser_class |
WhiskerJS’s own guess of the browser family, from client values |
automation_webdriver, automation_globals |
the browser or the page says it is driven by automation |
native_tampering, prototype_lie, iframe_mismatch |
something changed the values fingerprinting reads |
Each flag has documented false positives: privacy extensions trigger native_tampering and prototype_lie, your own end-to-end tests set automation_webdriver, and real users without GPU acceleration trigger software_renderer. Treat flags as features for your rules, never as a block list on their own.
Using structuredId similarity
An exact visitorId match breaks when one stable component changes, for example after a GPU driver update. The structuredId lets you see that only one group moved. A small example built on the comparison shown in Identifiers:
import { ID_GROUP_ORDER } from 'whiskerjs';
/** Example weights only: tune them on your own traffic. */
const WEIGHTS: Record<string, number> = {
gpu: 3, audio: 1, fonts: 2, device: 2, browser: 2, locale: 1, prefs: 1,
};
/** Share of the total weight in groups that match, or null when not comparable. */
function similarity(a: string, b: string): number | null {
const pa = a.split('_');
const pb = b.split('_');
if (pa[0] !== pb[0]) return null; // different idVersion
let same = 0;
let total = 0;
ID_GROUP_ORDER.forEach((group, i) => {
const w = WEIGHTS[group] ?? 1;
total += w;
if (pa[i + 1] === pb[i + 1]) same += w;
});
return same / total;
}
The weights and any threshold you put on the score are yours to choose. WhiskerJS publishes no recommended values, because none has been measured. Store which groups matched, not only the score, so a decision can be explained later.
Anti-replay
Without a binding, someone could record one clean result and send it with every fraudulent request. Issue a fresh nonce and timestamp from your server, pass them to collect({ binding }), and on receipt check that the nonce is yours, unused and recent, and that the digest still matches the result. See Anti-replay binding and the server guides for Node, Go, PHP and Python. The digest is not a signature: it stops replay of old results, not a fresh result built by hand.
What must happen on your server
- Check the shape and versions. Every component present, statuses valid, versions the ones you ship. See Recommended server checks.
- Verify the binding, if you use it, before any slow work.
- Compare with what you see yourself: IP region against
time_zone,Accept-Languageagainstlanguages, the User-Agent header againstua_client_hints. - Look at history and velocity. The same visitorId on many accounts, or one account on many visitorIds in a short time, usually says more than any single result.
- Decide with rules or a model you can explain, using the result as one input. Never let the device result alone grant access, approve a payment or release money.
- Store per event the ids, both version numbers, the flags and the components your rules use, with a short retention. See Storing ids.
Limits
- Anything produced in the browser can be faked by an attacker who controls it. A clean
integrityFlags: []proves nothing. - Privacy browsers are respected, not worked around: under Brave and Firefox resistFingerprinting some components are excluded, so those devices are less distinct. See Brave and Firefox resistFingerprinting and Tor Browser.
- Shared and identical devices (a family laptop, a fleet of company machines) can share an id.
- No accuracy, false-positive or uniqueness figures exist for WhiskerJS yet. Measure on your own traffic.
Questions and answers
Can WhiskerJS block fraudulent users?
No. It never blocks, scores or decides. It returns signals and flags; your server decides what to do with them, ideally together with account history and request data.
Should I collect on every page view?
Usually not. Collect at the moments that matter, such as sign-up, login, a change of payout details or checkout. That keeps the page fast and the data limited to its purpose.
Is a matching visitorId proof that two events came from the same person?
No. It means the same browser setup, as far as these signals can tell. Identical devices with identical settings can share an id, and one person can have several.