# WhiskerJS and ThumbmarkJS

> Two open-source collectors with a similar signal surface and very different defaults on telemetry and noise, compared from a code reading of ThumbmarkJS 1.12.0.

Source: https://whiskerjs.com/compare/thumbmarkjs/ (last updated 2026-10-26)

> **In plain words:**
>
> ThumbmarkJS is an MIT-licensed browser fingerprint library with a client part and an optional paid API. Its client reads a wide set of signals, including permissions, speech voices and WebRTC session details, and returns a hash. WhiskerJS reads a similar surface, keeps the easily changing parts out of its ids, and has no network code at all. The biggest practical difference is the defaults: ThumbmarkJS samples telemetry by default, WhiskerJS never sends anything.

## At a glance

|   | WhiskerJS 0.1.0 | ThumbmarkJS 1.12.0 |
| --- | --- | --- |
| Licence | Apache-2.0 | MIT |
| Version read | main at signalsVersion 9, idVersion 2 | tag v1.12.0, commit 7eeda65 |
| Last release | not yet published | 2026-09-25 |
| Status | in development | maintained |
| Kind | collector: signals, ids and flags, no verdict | device identifier (client library plus optional paid API) |
| Identifier | 32-hex `visitorId` plus a 7-part `structuredId` | hash of the components |
| Bot and tamper checks | 19 integrity flags | none client-side |
| Default network behaviour | none | sampled logging on about 1 in 10000 runs, with a remote script fetched and evaluated on that run |
| Off switch | not needed | `logging: false`, no `api_key`, `collect_beacon: false` |
| Install | build from source; npm package coming | npm package `@thumbmarkjs/thumbmarkjs` |

How this was made: ThumbmarkJS’s public repository was read at v1.12.0 on 2026-10-24 (code read, never installed or run). Its own documentation is the reference if anything here differs.

## Signals

**Y** collected for the identifier · **b** used only as a bot, lie or consistency check · **o** opt-in, or collected but outside the hash · **-** not collected.

| Signal | WhiskerJS 0.1.0 (signalsVersion 9) | ThumbmarkJS 1.12.0 |
| --- | --- | --- |
| Canvas 2D image | Y (double render, known-answer probe) | Y (3 renders, common pixel) |
| WebGL vendor and renderer (unmasked) | Y | Y |
| WebGL parameters and extensions | Y | - |
| WebGL rendered image | Y (double render, known-answer probe) | Y |
| WebGPU adapter info | Y | - |
| Audio (OfflineAudioContext) | Y | Y |
| Audio base latency | o (Safari and Android only) | - |
| Fonts (how probed) | Y (54, span width and canvas measureText) | Y (89, canvas measureText, iframe) |
| Font preferences (default font sizes) | Y | - |
| Screen size and colour depth | Y | Y |
| Screen frame or available area | o | - |
| Time zone | Y | Y |
| Intl locale and calendars | Y | - |
| Languages | Y | Y |
| User-Agent and platform | Y (platform; the UA string only for the class and checks) | Y |
| UA Client Hints (high entropy) | Y (versions outside the id) | - |
| Hardware concurrency | Y | Y |
| Device memory | Y | Y |
| JS heap limit | Y | Y |
| CPU architecture (NaN trick or CH) | Y (NaN) | - |
| Touch support | Y | Y |
| Plugins and mimeTypes | Y (+ consistency check) | Y |
| Math precision | Y | Y |
| CSS media features | Y (5) | Y (11) |
| Storage availability | Y | - |
| Cookies enabled | Y | Y |
| Media codecs | Y | - |
| Speech voices | o | Y |
| Permissions states | o (24) | Y (28) |
| Media device counts | - | - |
| WebRTC | - | Y (SDP, no IP) |
| Network information | - | - |
| Battery | - | - |
| Keyboard layout | o | - |
| Content blockers or extensions | o (8 bait elements) | - |
| Vendor-specific globals | Y | Y (Apple Pay) |
| Apple Pay, Private Click Measurement | Y | Y (Apple Pay) |
| PDF viewer | Y | - |
| Automation markers (webdriver, Selenium, Playwright, CDP, PhantomJS) | b (webdriver, 32 globals, Playwright binding, headless; no CDP) | - |
| Lie or tamper detection | b (native source, descriptors, clean iframe, Proxy, cross-API consistency) | - |
| iframe and Worker cross-check | b (iframe; no Worker) | Y (iframe for fonts) |
| Anti-replay (nonce, time) | Y | - |
| Identifier format | 32-hex visitorId plus 7-part structuredId (previous ids after a bump) | hash |

In short: both read canvas, a WebGL image, WebGL identity, audio, fonts by canvas `measureText`, Math, the JS heap limit, screen, languages, time zone, permissions and speech voices. ThumbmarkJS also reads WebRTC session details (with no ICE servers, so no address) and more media queries; WhiskerJS does not touch WebRTC at all. WhiskerJS also reads WebGL parameters, WebGPU, Client Hints, Intl locale details, media codecs, storage availability and font preferences, and runs integrity checks.

## How they differ in approach

**Canvas and WebGL noise.** ThumbmarkJS renders its canvas three times (its WebGL image once, or three times in Samsung Browser) and keeps the most frequent byte per pixel, which reconstructs a usable image even when a browser adds per-read noise. WhiskerJS takes the opposite position: it renders twice, and if the two differ the component is `noisy` and carries no value; known-answer probes (a canvas, WebGL buffer and sound whose exact bytes are known) also catch noise seeded per session, which two renders cannot see (`noisy` with ruleId `seeded_noise`); and for Brave, Firefox Fingerprinting Protection and resistFingerprinting, Tor and Mullvad Browser, and Safari Advanced Fingerprinting Protection it does not collect the randomised components at all. That keeps a noised value out of the id instead of working around a privacy feature. See [Noise handling](https://whiskerjs.com/docs/how-it-works/#noise-handling-the-double-render-and-the-known-answers).

**Stability.** ThumbmarkJS has an option `stabilize` (default `['private', 'iframe']`) that drops components known to change in private windows and iframes. WhiskerJS has no such presets; instead ten components that change easily (pixel ratio, screen frame, versions, permissions, keyboard layout, voices, audio latency, canvas readback, noise profile, content blockers) are always reported but never part of the ids, and you can drop any component with `collect({ exclude })`.

**Permissions.** Both read permission states only through `navigator.permissions.query`, which never prompts: ThumbmarkJS over 28 names inside its hash, WhiskerJS over 24 names outside its ids (states change when users grant or revoke).

**Network.** ThumbmarkJS’s sampled logging posts all components and the page path to its logging API, and on that run fetches and evaluates `experimental.min.js` from its own host; with an API key it also calls its API and may send a beacon. WhiskerJS has no network code, no telemetry and an optional `onResult` hook that you wire to your own endpoint.

**Identifier and storage.** ThumbmarkJS keeps the API’s visitor id in localStorage when the paid API is used. WhiskerJS stores nothing and returns a versioned `visitorId` and a part-by-part `structuredId`.

**Integrity and anti-replay.** WhiskerJS adds 19 integrity flags and an optional nonce binding. ThumbmarkJS’s client does not include automation or lie detection in the version read.

## Their claims, in their words

- `README.md` (1.12.0): “The client library achieves ~80% uniqueness across general browser populations. Mac/Safari users can show higher collision rates or higher noise depending on the audience.”
- `README.md` (1.12.0): the paid API claims “over 99% uniqueness” with server-side signals.
- Option `stabilize`: “Preset exclusion list for stability across private browsing and iframes”.
- `README.md`, sampled logging: the samples show “which components turn out to be unstable across real browsers.”

WhiskerJS publishes no accuracy, stability or uniqueness figure yet.

## Code credits

WhiskerJS contains no ThumbmarkJS code. Its `THIRD_PARTY_NOTICES.md` credits ThumbmarkJS 1.12.0 for ideas only: the component layout, the offline audio graph, speech voices as a signal, permission states, the JS heap limit and font detection by canvas `measureText`. Its API and telemetry calls were not used.

## When you might pick either

- You want a registry package today and may later add a paid server-side API: ThumbmarkJS offers that path.
- You want a value even in browsers that add per-read canvas noise: ThumbmarkJS’s common-pixel reduction is designed for that.
- You must guarantee that no request and no remote code leaves or enters the page through the library: WhiskerJS has none; with ThumbmarkJS set `logging: false` and leave out the API options.
- You need statuses per component, a part-by-part id, integrity flags or a nonce binding: those are WhiskerJS features.
- Either way, measure on your own traffic.

> **No head-to-head measurement has run.** This page compares features and designs. It does not say that either library is more accurate, more stable or more unique. A side-by-side measurement on the same devices is planned and will be published on the [comparison page](https://whiskerjs.com/compare/).

## Questions and answers

### Does ThumbmarkJS send data by default?

In 1.12.0, sampled logging is on by default: on about 1 in 10000 runs it posts the components and the page path to its logging API and, on the same run, fetches and evaluates a remote script. logging: false turns it off. API and beacon calls happen only with an API key or after an API response.

### ThumbmarkJS says about 80 percent uniqueness. What does WhiskerJS say?

Nothing yet. The 80 percent figure is ThumbmarkJS's own claim for its client library across general browser populations. WhiskerJS publishes no uniqueness figure until its measurement has run.

### How do the two handle canvas noise?

ThumbmarkJS renders the canvas several times and keeps the most common byte per pixel. WhiskerJS renders twice and, if the two differ or a known-answer probe shows seeded noise, reports the component as noisy and leaves it out instead of reconstructing a value.

## Related pages

- [Feature comparison matrix](https://whiskerjs.com/compare/): All twelve libraries on one table.
- [Open-source alternatives](https://whiskerjs.com/compare/open-source-alternatives/): Every library we read, described the same way.
- [canvas](https://whiskerjs.com/signals/canvas/): WhiskerJS's canvas signal and its double render.
- [fonts_canvas](https://whiskerjs.com/signals/fonts-canvas/): Font detection by measureText, an idea credited to ThumbmarkJS.
- [Privacy](https://whiskerjs.com/docs/privacy/): No network, no storage, no prompts.
