Open source · In development · Apache-2.0
WhiskerJS is an open-source browser collector for device signals: 47 signals in 7 groups, 19 integrity checks and two versioned identifiers, hashed in the page and handed to your server to judge. No storage, no verdicts, and the result goes only where you send it.
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 11 · idVersion 2
One call, one plain JSON result. What changed in each version.
Graphics, audio, fonts, device, browser, locale and preferences, plus 10 volatile signals reported outside the ids. Each with a status and a reason code.
Read moreCanvas and WebGL are drawn twice, and up to twice more when the first two differ; canvas, WebGL and audio are checked against known answers; noise is reported as noisy instead of trusted. Signals that Brave, Firefox and Safari fingerprinting protection, resistFingerprinting and Tor randomise are left out by an exclusion table.
Read moreAutomation globals, webdriver, headless traits, engine, user agent, time zone and screen mismatches, impossible hardware, virtual machines, 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 never sends the result itself; its only request is a sampled diagnostics report of statuses, which one setting turns off.
Read moreHow it works
The library never scores or blocks, and never sends the result anywhere. Every decision stays in your code. The pipeline in detail.
Step 1
47 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, WebGL and audio are checked twice and against known answers to catch noise.
Step 2
Large values become SHA-256 digests in the page. 19 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 and Safari fingerprinting protection add noise, resistFingerprinting and Tor fake others, and private windows can add canvas noise. WhiskerJS knows where, draws twice (and again when the two differ) and checks known answers 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 44441d3, signalsVersion 11, idVersion 2) in your browser: the visitorId, the structuredId group by group, every component with its status, the integrity flags and, after a library update, the id under the previous signal version for linking. Run it again later to see whether your id holds, and if not, which group moved. The demo turns the diagnostics report off, so 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.
Explore
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.
By default about 1 in 100 page loads send the WhiskerJS project one report of component statuses, never values or ids. diagnostics: false, a meta tag, Do Not Track or Global Privacy Control turns it off. What is sent.
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