Open source · In development · Apache-2.0
WhiskerJS is an open-source browser collector for device signals: 46 signals in 7 groups, 14 integrity checks and two versioned identifiers, hashed in the page and handed to your server to judge. No network calls, no storage, no verdicts.
Collect a result and send it with the request you already make. No extra endpoint, no third-party calls.
Not on npm yet: version 0.1.0 is built from source. The API may change before 1.0. Getting started
$ pnpm install && pnpm build # in the WhiskerJS source folder
import { load } from 'whiskerjs' const agent = await load() const device = await agent.collect({ binding: { nonce, timestamp } }) await fetch('/login', { method: 'POST', body: JSON.stringify({ email, password, device }) })
Features · signalsVersion 5 · idVersion 2
One call, one plain JSON result. What changed in each version.
Graphics, audio, fonts, device, browser, locale and preferences, plus 9 volatile signals reported outside the ids. Each with a status and a reason code.
Read moreCanvas and WebGL are drawn twice; a mismatch is reported as noisy instead of trusted. Brave farbling and Firefox resistFingerprinting signals are left out by an exclusion table.
Read moreAutomation globals, webdriver, headless traits, engine mismatches, impossible hardware, and lie and Proxy detection against a clean, hidden iframe. Reported as flags, never as a verdict.
Read morevisitorId for an exact match, and structuredId with one short hash per group, so "same device, new graphics driver" is visible instead of a new id.
Read morePass your server's single-use nonce and timestamp; the result carries a digest over everything, so an old result cannot be replayed. Signing stays on your server.
Read moreonResult hands your code a copy of every result. Off by default. The library itself never sends anything.
Read moreHow it works
The library never scores, blocks or phones home. Every decision stays in your code. The pipeline in detail.
Step 1
46 signals run in parallel, each with one second and the whole call with a budget. Signals a browser is known to randomise are left out instead of trusted, and canvas and WebGL are drawn twice to catch noise.
Step 2
Large values become SHA-256 digests in the page. 14 checks look for automation and tampering. The result is versioned and, if you pass your server's nonce, bound to it with a digest so old results cannot be replayed.
Step 3
Send the result with a request you already make. Match visitorIds, compare structuredIds group by group, weigh the flags and set thresholds in your own code. Your rules stay private.
Inside a result
Brave randomises some signals, Firefox's resistFingerprinting mode fakes others, and private windows can add canvas noise. WhiskerJS knows where, draws twice to catch the rest, and gives every signal a status and a reason code, so your server knows what it could not trust.
Live demo
The demo runs the real WhiskerJS build (commit 1100e48, signalsVersion 5, idVersion 2) in your browser: the visitorId, the structuredId group by group, every component with its status, and the integrity flags. Run it again later to see whether your id holds, and if not, which group moved. Nothing is sent anywhere.
Comparison
Which signals each open-source library collects at a named version, its licence, its own stability and uniqueness claims, and which ones send data home by default. The head-to-head measurement has not run yet, so the page makes no accuracy claim; it is coming.
Privacy
Device signals can be personal data. The lawful basis and the notice belong to the site that uses WhiskerJS; the library keeps what it touches to a minimum. What is and is not collected, and a notice template.
No network requests of any kind: no usage sampling, no remote scripts.
No permission prompts. No WebRTC addresses, battery, location or media device labels.
Images, audio, font lists and other large values leave only as SHA-256 digests.
No cookies, localStorage or IndexedDB. Each call starts from nothing.
WhiskerJS learns from the open-source projects that came before it. Two small parts are adapted with their MIT notices kept (Math operations from FingerprintJS 5.3.0, media MIME lists from fpscanner 1.0.8); the rest is original code built from published ideas, credited in the library's third-party notices.
FingerprintJSThumbmarkJSBotDfpscannerBowserClientJS