A signal (WhiskerJS calls it a component) is one small measurement of the browser and device, such as the screen size, the time zone or how a test image is drawn. One signal alone says little; together they tell browser setups apart. WhiskerJS reads 46 of them, sorts the 37 that rarely change into seven groups, and keeps the 9 that change easily out of the identifiers. It never decides what a signal means: your server does.
How to read a signal page
Every signal page has the same parts, so you can compare them:
- Facts box (at the top): the group, whether the signal is in the visitorId, how it is hashed, whether a browser class excludes it, and the signalsVersion that added it. It is generated from the library’s code.
- What it reads: the exact browser API and what is taken from it.
- How the technique works: the method, step by step.
- Why it helps: what makes it differ between devices.
- Stability: what makes it change on the same device.
- Browser support and what blocks it: where it is usually
ok, and what it reports elsewhere. - Privacy: what the value can reveal and how WhiskerJS limits that.
- How WhiskerJS handles noise and failures: the statuses and ruleIds this component can really report.
- Example value: the shape of a real value.
If you would rather read everything on one page, use the signals reference.
The groups at a glance
The structuredId has one part per signal group. When two results differ, the group that moved tells you what kind of change happened. Columns: In visitorId says whether the component joins both ids; Always hashed says whether the value always leaves the library as a digest (other values are hashed only when longer than 128 characters or not printable ASCII).
gpu: graphics
Graphics output depends on the GPU, its driver, the operating system and the browser’s rendering engine together. It is the most distinguishing group and also the one most likely to move after a driver or browser update.
| Component | Group | In visitorId | Always hashed |
|---|---|---|---|
canvas |
gpu | yes | yes |
webgl_image |
gpu | yes | yes |
webgl_params |
gpu | yes | yes |
webgl_renderer |
gpu | yes | no |
webgl_vendor |
gpu | yes | no |
webgpu |
gpu | yes | yes |
audio
One offline audio render. Nothing is played and no audio device is opened.
| Component | Group | In visitorId | Always hashed |
|---|---|---|---|
audio |
audio | yes | yes |
fonts
Installed fonts and default font settings, which follow the operating system, its language packs and installed software.
| Component | Group | In visitorId | Always hashed |
|---|---|---|---|
font_preferences |
fonts | yes | yes |
fonts |
fonts | yes | yes (none plain) |
fonts_canvas |
fonts | yes | yes (none plain) |
device: screen, memory and CPU
Hardware facts the browser reports: screen size, colour depth, CPU threads and family, memory buckets and touch support.
| Component | Group | In visitorId | Always hashed |
|---|---|---|---|
color_depth |
device | yes | no |
cpu_architecture |
device | yes | no |
device_memory |
device | yes | no |
hardware_concurrency |
device | yes | no |
heap_limit |
device | yes | no |
screen_resolution |
device | yes | no |
touch_support |
device | yes | no |
browser: engine and build
What the browser is and how it was built: its class, its maths and codec support, plugins, Client Hints and browser-specific globals. This group usually moves after a browser update.
| Component | Group | In visitorId | Always hashed |
|---|---|---|---|
apple_pay |
browser | yes | no |
browser_class |
browser | yes | no |
math |
browser | yes | yes |
media_codecs |
browser | yes | yes |
pdf_viewer |
browser | yes | no |
platform |
browser | yes | no |
plugins |
browser | yes | yes (none plain) |
private_click |
browser | yes | no |
storage |
browser | yes | no |
ua_client_hints |
browser | yes | no |
vendor |
browser | yes | no |
vendor_flavors |
browser | yes | no |
locale
Language, locale and time zone settings. They move when a user changes them or travels.
| Component | Group | In visitorId | Always hashed |
|---|---|---|---|
intl_locale |
locale | yes | no |
languages |
locale | yes | no |
time_zone |
locale | yes | no |
prefs: display and accessibility preferences
Five CSS media queries about the screen and the user’s accessibility settings. Each reports the first option that matches.
| Component | Group | In visitorId | Always hashed |
|---|---|---|---|
color_gamut |
prefs | yes | no |
dynamic_range |
prefs | yes | no |
forced_colors |
prefs | yes | no |
prefers_contrast |
prefs | yes | no |
reduced_motion |
prefs | yes | no |
Not in either id
These nine are reported so your server can use them, but they change too easily (zoom, taskbar, permission grants, keyboard layout, late-loading voices, browser versions, audio output, content blockers) to be part of an identifier. Changing them never changes the visitorId or the structuredId.
| Component | Group | In visitorId | Always hashed |
|---|---|---|---|
audio_latency |
none | no | no |
canvas_readback |
none | no | no |
content_blockers |
none | no | no |
keyboard_layout |
none | no | no |
permissions |
none | no | yes |
pixel_ratio |
none | no | no |
screen_frame |
none | no | no |
speech_voices |
none | no | yes (none plain) |
ua_versions |
none | no | no |
What every signal has in common
- Always present. Every one of the 46 components is in every result with exactly one status:
ok,unavailable,blocked,noisy,excludedortimed_out. A non-ok status carries a short ruleId saying why. See Statuses and reason codes. - Its own clock. Each component has an inner timeout of 1000 ms, and the whole call has a budget (
timeoutMs, 5000 ms by default). - No prompts, no network, no storage. No signal asks for a permission, makes a request or writes anything. See Privacy.
- Excluded where a browser randomises it on purpose. Brave’s farbling and Firefox resistFingerprinting remove some components through the exclusion table, instead of WhiskerJS working around them.
Left out on purpose
WebRTC (even without addresses), media device lists, network information, battery, geolocation, storage quota and user-agent parsing are not collected, because they need a network-capable API, a permission-coupled API or console output, or because they change too easily. See the Signals reference.
Questions and answers
How many signals does WhiskerJS read?
46 components at signalsVersion 5. 37 of them are stable and make up the visitorId and the seven parts of the structuredId; the other 9 are reported but kept out of both ids.
Can I turn a signal off?
Yes. Pass its name in collect({ exclude: [...] }). It then reports status excluded with ruleId caller_excluded, and a stable component counts as missing in both ids, so a different exclude list gives different ids.
Are the uniqueness or entropy of each signal measured?
Not yet. No per-signal entropy or uniqueness figure for WhiskerJS has been measured, so these pages describe why a signal tends to differ between devices without giving numbers.