WhiskerJS is an open-source (Apache-2.0) script for web pages. It reads technical characteristics of the visitor’s browser and device, such as screen size, fonts and how a test image is drawn, and returns them as one JSON object. Your own server then decides what to do with it: recognise a device it has seen before, or notice that something looks automated. WhiskerJS itself never decides, never sends anything and never stores anything.
What you get from one call
import { load } from 'whiskerjs';
const agent = await load();
const result = await agent.collect();
| Field | What it is |
|---|---|
components |
46 measurements, each with a status (ok, unavailable, blocked, noisy, excluded, timed_out) and, when not ok, a reason code |
visitorId |
a 32-character hash of the 37 stable components: equal ids mean the same browser setup |
structuredId |
seven 12-character hashes, one per signal group, so two results can be compared group by group |
integrityFlags |
names of the 14 checks for automation and tampering that fired |
binding |
your server’s nonce and timestamp, bound to the result with a digest (only if you pass them) |
version, signalsVersion, idVersion |
0.1.0, 5, 2: store them with every id |
durationMs |
how long the collection took |
What it is and is not
| WhiskerJS is | WhiskerJS is not |
|---|---|
| a collector of device and browser signals | a bot verdict or a risk score |
| a library that runs in your page | a hosted service or an API |
| stateless: every call starts from nothing | a tracker: it stores no cookie or id |
| silent on the network | telemetry: it never phones home |
| open source under Apache-2.0 | on npm yet (build it from source) |
The pages
Reading the live demo
The live demo runs the real library (the build of commit
1100e48, signalsVersion 5, idVersion 2) in your browser and shows exactly
what it returns. Nothing is sent anywhere; the demo page keeps a history of
your runs in your own browser so it can compare them.
- visitorId (top left): the id for this browser setup. The chip under it says whether it matches your previous run on this device.
- Version tiles:
version,signalsVersionandidVersionof the library, the run’s duration, how many components areok, and how many times you have loaded the page. - structuredId: the full value and one tile per group. After a second
run, a group whose part changed is highlighted, which tells you what
moved (for example
gpuafter a graphics driver update). - Integrity flags: usually empty on a normal browser. “What every flag
means” explains each one; see Integrity checks for the
false positives (privacy extensions often set
native_tampering). - Components table: every component with its status, the reason code
when it is not ok, whether it counts in the visitorId, and its value.
Values that are 32 hex characters are digests, not raw data. Common
non-ok rows are normal:
device_memoryandheap_limitareunavailableoutside Chromium-based browsers,audio_latencyisunavailablewithskippedoutside Safari and Android, and under Brave several components areexcludedwithbrave_farbling. - History: newest first, with the groups and components that changed since the run before. Runs from different library versions are never compared; the list marks the boundary.
- Lab testing mode: label the device, load the page a few times, and copy one tab-separated line per run into a sheet to measure stability on your own devices. “Copy full JSON” includes the anti-replay binding (the demo makes its own nonce; a real site gets it from its server).
What to try: run it twice (the id should hold), open a private window (some
browsers add canvas noise there, and canvas or webgl_image turn
noisy), or change the browser language (usually only the locale part
of the structuredId changes).