A signal (or component) is one measurement, such as the screen size or the list of installed fonts. Each one differs a little between devices; together they tell devices apart. 37 signals rarely change and form the visitorId, sorted into seven groups (graphics, audio, fonts, device, browser, locale, preferences). Nine change too easily (zoom, taskbar, permissions and so on), so they are reported but kept out of the ids.
Every component also has its own page, with how the technique works, stability, browser support, privacy and every status it can report: see Signals and the glossary.
WhiskerJS reports 46 components. 37 of them are stable and make up the visitorId; each stable component belongs to exactly one of seven groups (the parts of the structuredId). The other 9 are reported for your server to use but kept out of both ids, because they change too easily.
How to read each entry:
- Reads: the browser API and exactly what is taken from it.
- Why it helps: what makes it differ between devices.
- Typical value: the shape of an
okvalue. Values shown as “digest” are 32 hex characters (see How it works). - Support: where it is usually
ok, and what it reports elsewhere.
Values in the examples are illustrative, not from a particular device.
Overview
| Component | Group | In visitorId | Always hashed |
|---|---|---|---|
apple_pay |
browser | yes | no |
audio |
audio | yes | yes |
audio_latency |
none | no | no |
browser_class |
browser | yes | no |
canvas |
gpu | yes | yes |
canvas_readback |
none | no | no |
color_depth |
device | yes | no |
color_gamut |
prefs | yes | no |
content_blockers |
none | no | no |
cpu_architecture |
device | yes | no |
device_memory |
device | yes | no |
dynamic_range |
prefs | yes | no |
font_preferences |
fonts | yes | yes |
fonts |
fonts | yes | yes (none plain) |
fonts_canvas |
fonts | yes | yes (none plain) |
forced_colors |
prefs | yes | no |
hardware_concurrency |
device | yes | no |
heap_limit |
device | yes | no |
intl_locale |
locale | yes | no |
keyboard_layout |
none | no | no |
languages |
locale | yes | no |
math |
browser | yes | yes |
media_codecs |
browser | yes | yes |
pdf_viewer |
browser | yes | no |
permissions |
none | no | yes |
pixel_ratio |
none | no | no |
platform |
browser | yes | no |
plugins |
browser | yes | yes (none plain) |
prefers_contrast |
prefs | yes | no |
private_click |
browser | yes | no |
reduced_motion |
prefs | yes | no |
screen_frame |
none | no | no |
screen_resolution |
device | yes | no |
speech_voices |
none | no | yes (none plain) |
storage |
browser | yes | no |
time_zone |
locale | yes | no |
touch_support |
device | yes | no |
ua_client_hints |
browser | yes | no |
ua_versions |
none | no | no |
vendor |
browser | yes | no |
vendor_flavors |
browser | yes | no |
webgl_image |
gpu | yes | yes |
webgl_params |
gpu | yes | yes |
webgl_renderer |
gpu | yes | no |
webgl_vendor |
gpu | yes | no |
webgpu |
gpu | yes | yes |
“No” in the last column still means: hashed when the value is longer than 128 characters or not printable ASCII.
The exclusion table removes some of these under Brave and Firefox resistFingerprinting; see How it works.
gpu: graphics
Graphics output depends on the GPU, its driver, the operating system and the browser’s rendering engine together, which makes this the most distinguishing group and also the one that changes after a driver update.
canvas
- Reads: a 260x80 2D canvas scene (a filled rectangle, the text
“Whiskers twitch, jumpy fox” with a cat emoji in Times New Roman and Arial,
three circles with
multiplyblending, an even-odd ring), exported withtoDataURL(). Rendered twice. - Why it helps: font rasterising, anti-aliasing, emoji images and blending differ between systems.
- Typical value: digest.
- Support: all browsers.
noisy/render_mismatchwhere readback is randomised;excludedunder Brave and Firefox resistFingerprinting.
webgl_image
- Reads: a 32x32 WebGL drawing of one triangle with a procedural colour
shader, read back with
readPixels(). Drawn twice. - Why it helps: shader maths and rasterising differ between GPUs and drivers.
- Typical value: digest.
- Support: where WebGL 1 is available.
unavailable/no_contextwithout WebGL;noisywhen readback is randomised;excludedunder Brave and Firefox resistFingerprinting.
webgl_params
- Reads: 21 WebGL parameters (texture sizes, viewport limits, bit depths, shading language version and others), the range and precision of four float and int formats in both shader types, and the sorted list of supported extensions.
- Why it helps: limits and extensions follow the GPU, driver and browser.
- Typical value: digest.
- Support: where WebGL 1 is available.
webgl_renderer
- Reads: the unmasked renderer string from
WEBGL_debug_renderer_infowhere the extension exists, otherwisegl.RENDERER. In Firefox the extension is not requested (it prints a deprecation warning there) andgl.RENDERERalready carries the unmasked string. - Why it helps: names the GPU, often with the graphics back end.
- Typical value:
ANGLE (Apple, ANGLE Metal Renderer: Apple M2, Unspecified Version); hashed when longer than 128 characters. - Support: where WebGL 1 is available.
excludedunder Firefox resistFingerprinting. A software renderer here also sets thesoftware_rendererflag.
webgl_vendor
- Reads: the unmasked vendor string, same method as
webgl_renderer. - Typical value:
Google Inc. (Apple). - Support: as
webgl_renderer.
webgpu
- Reads:
navigator.gpu.requestAdapter(), then the adapter’sinfo(vendor, architecture, device, description), its sorted feature list and seven limits (maxBindGroups,maxBufferSize,maxComputeInvocationsPerWorkgroup,maxComputeWorkgroupStorageSize,maxStorageBufferBindingSize,maxTextureDimension2D,maxVertexBuffers). No device is requested, and the deprecatedrequestAdapterInfo()(which warns in the console) is not called. - Why it helps: a second, newer view of the GPU.
- Typical value: digest.
- Support: browsers with WebGPU, in secure contexts.
unavailable/no_apiwithoutnavigator.gpu,no_contextwhen no adapter is returned.
audio
audio
- Reads: an
OfflineAudioContext(1 channel, 5000 frames at 44100 Hz) renders a 10 kHz triangle wave through a dynamics compressor; the last 500 samples are taken. Nothing is played and no audio device is opened. - Why it helps: the floating-point details of the audio stack differ between engines, operating systems and CPUs.
- Typical value: digest.
- Support: all current browsers.
blocked/silentwhen the render is all zeros;excludedunder Brave.
fonts
Which fonts are installed, and how the default fonts are set, depend on the operating system, its language packs and installed software.
fonts
- Reads: for each of 54 font families, text in that font with a generic
fallback (
monospace,sans-serif,serif) is measured in hidden spans and compared with the fallback alone. A family counts as present when any width or height differs. The spans sit in one hidden, off-screen container that is removed at once. - Why it helps: installed fonts vary a lot between systems.
- Typical value: digest of the sorted list of found families, or
none. - Support: needs a
document.bodyand layout (unavailable/no_documentorno_layoutotherwise).excludedunder Brave.
fonts_canvas
- Reads: the same 54 families, detected a second way: canvas
measureText()widths at 72 px against the three fallbacks. No DOM layout, so page CSS cannot disturb it. - Typical value: digest, or
none. - Support: all browsers with a 2D canvas.
excludedunder Brave.
font_preferences
- Reads: the width of the text “Whiskers quietly judge 0123456789” in
seven hidden spans: the browser default font,
serif,sans-serif,monospace,system-ui,-apple-system, and the default font at 1 px (which reveals the minimum font size setting). - Why it helps: follows the user’s font settings and the operating system’s system font.
- Typical value: digest.
- Support: needs a body and layout.
device: screen, memory and CPU
screen_resolution
- Reads:
screen.widthandscreen.height, larger side first, so that rotating a phone does not change it. - Typical value:
1920x1080,2560x1440,932x430. - Support: all browsers.
excludedunder Firefox resistFingerprinting.
color_depth
- Reads:
screen.colorDepth. - Typical value:
24,30.
hardware_concurrency
- Reads:
navigator.hardwareConcurrency(logical CPU threads). - Typical value:
8,12. - Support: all current browsers. Some browsers cap or round it.
excludedunder Brave and Firefox resistFingerprinting.
device_memory
- Reads:
navigator.deviceMemory(approximate GB, coarse buckets). - Typical value:
8,4. - Support: Chromium-based browsers only. Safari, iOS browsers and
Firefox do not offer it, so it is
unavailable/no_apithere by design.
heap_limit
- Reads:
performance.memory.jsHeapSizeLimit. - Typical value:
4294705152. - Support: Chromium-based browsers only;
unavailableelsewhere.
cpu_architecture
- Reads: computes
Infinity - Infinityin aFloat32Arrayand reports the high byte of the resulting NaN’s bit pattern. - Why it helps: x86 produces a NaN with the sign bit set, ARM one without, so it tells CPU families apart even when the user agent hides them.
- Typical value:
255(x86),127(ARM).
touch_support
- Reads:
navigator.maxTouchPoints, whetherdocument.createEvent('TouchEvent')works, and whetherontouchstartexists on the window. - Typical value:
points=0,event=0,start=0(desktop),points=5,event=1,start=1(phone).
browser: engine and build
browser_class
- Reads: WhiskerJS’s own class:
brave,firefox_rfp,firefox,safari,chromiumorother, fromnavigator.brave, the user agent,navigator.vendorand the resistFingerprinting traits. - Why it helps: picks the exclusion row; separates browser families.
- Typical value:
chromium.
math
- Reads: 24 Math results (acos, acosh, asin, asinh, atanh, atan, sin,
sinh, cos, cosh, tan, tanh, exp, expm1, log1p and polyfill forms of some,
plus
PIto the power -100) on fixed inputs. The operations and inputs are adapted from FingerprintJS 5.3.0 (MIT). - Why it helps: the last digits of these functions differ between JavaScript engines and their versions.
- Typical value: digest.
media_codecs
- Reads: for 8 audio and 12 video MIME types,
canPlayType()on a new<audio>or<video>element (never given a source, never attached) andMediaSource.isTypeSupported(). The MIME lists are adapted from fpscanner 1.0.8 (MIT). - Typical value: digest.
plugins
- Reads:
navigator.plugins: each plugin’s name, file name and MIME types, sorted. - Why it helps: mostly a fixed PDF plugin list in current browsers, but it differs between engines and some embedded browsers.
- Typical value: digest, or
none. - Support:
excludedunder Brave.
pdf_viewer
- Reads:
navigator.pdfViewerEnabled. - Typical value:
trueorfalse;unavailablewhere the property does not exist.
platform
- Reads:
navigator.platform. - Typical value:
MacIntel,Win32,Linux x86_64,iPhone.
vendor
- Reads:
navigator.vendor, with an empty string reported asempty. - Typical value:
Google Inc.,Apple Computer, Inc.,empty(Firefox).
vendor_flavors
- Reads: which of ten browser-specific globals exist (
chrome,safari,webkit,opr,yandex,__crWeb,__gCrWeb,__firefox__,samsungAr,ucweb). - Why it helps: tells browser builds and in-app browsers apart.
- Typical value:
chrome, ornone.
storage
- Reads: whether
localStorage,sessionStorageandindexedDBare reachable (the property is only looked up, never read or written) andnavigator.cookieEnabled. No cookie is read or set. - Typical value:
local=1,session=1,indexed_db=1,cookies=1.
ua_client_hints
- Reads:
navigator.userAgentData: brand names (without versions, with the deliberately changing “GREASE” brand removed),mobile,platform, and from onegetHighEntropyValues()call: architecture, bitness, wow64, model and form factors. - Why it helps: the device class and architecture, more reliably than the user agent string.
- Typical value:
brands=Chromium,Google Chrome;mobile=0;platform=macOS;arch=arm;bitness=64;wow64=0;model=;form=Desktop. When the detailed hints are refused by a Permissions Policy, the end is the stable markerhe=blocked(orhe=failed,he=none). - Support: Chromium-based browsers;
unavailableelsewhere.
apple_pay
- Reads: whether
ApplePaySessionexists, the highest version from 20 down thatsupportsVersion()accepts, andcanMakePayments(). No payment sheet is shown and no request is made. - Typical value:
none(no Apple Pay),version=14;can_pay=1.can_payisblockedwhere the call throws (insecure or cross-origin contexts). - Support: Safari;
noneelsewhere.
private_click
- Reads:
attributionSourceIdof a new<a>element that gets nohrefand is never attached (Safari Private Click Measurement). - Typical value:
noneoutside Safari.
locale
time_zone
- Reads: the IANA name from
Intl.DateTimeFormat().resolvedOptions(), or, without Intl,UTC+nfrom the larger of the January and July offsets. - Typical value:
Europe/Berlin,America/New_York. - Support:
excludedunder Firefox resistFingerprinting.
languages
- Reads:
navigator.languagesjoined with commas, ornavigator.language. - Typical value:
en-US,en.
intl_locale
- Reads: the resolved locale, calendar and numbering system of
Intl.DateTimeFormat, its hour cycle, and the locale ofIntl.NumberFormat. - Typical value:
locale=en-US;calendar=gregory;numbering=latn;hour_cycle=h12;number_locale=en-US.
prefs: display and accessibility preferences
Each is one matchMedia() query list; the first match is reported, and
unavailable / no_match when none matches.
| Component | Values |
|---|---|
color_gamut |
rec2020, p3, srgb |
dynamic_range |
high, standard |
forced_colors |
active, none |
prefers_contrast |
more, less, custom, none |
reduced_motion |
reduce, none |
They follow the screen and the user’s accessibility settings, which rarely change.
Not in either id
These are reported so your server can use them, but they change too easily to be part of an identifier.
pixel_ratio
- Reads:
devicePixelRatio, rounded to 3 decimals. - Typical value:
1,2,1.25. - Why not in the id: it changes with page zoom.
excludedunder Firefox resistFingerprinting.
screen_frame
- Reads: the screen area the system keeps (taskbar, dock, menu bar) as
top,right,bottom,leftin CSS pixels, fromscreenminus theavail*values. - Typical value:
0,0,48,0(a 48 px taskbar at the bottom). - Why not in the id: it changes when the taskbar moves or with a second
monitor.
excludedunder Firefox resistFingerprinting.
ua_versions
- Reads: full brand versions and the platform version from Client Hints.
- Typical value:
brands=Chromium/130.0.6723.58,Google Chrome/130.0.6723.58;platform_version=15.1.0. - Why not in the id: it changes with every browser or OS update. Chromium-based browsers only.
permissions
- Reads: the state (
granted,denied,prompt;unsupportedorunknownotherwise) of 24 permissions throughnavigator.permissions.query(), which never prompts. No API that requests a permission is ever called. - Typical value: digest.
- Why not in the id: it changes when the user grants or revokes a
permission. The notifications state also feeds the
permissions_inconsistentflag.
keyboard_layout
- Reads: the character 19 physical keys produce in the active layout,
from
navigator.keyboard.getLayoutMap()(no prompt);_for a key the layout lacks. - Typical value:
qwyzam;'[]`\\-=,./2(US English); hashed for layouts outside printable ASCII. - Why not in the id: users switch layouts. Chromium only;
blockedin cross-origin iframes, where the browser refuses it.
speech_voices
- Reads: installed speech synthesis voices (name, language, local,
default), sorted. Waits up to 600 ms for the
voiceschangedevent if the list is empty at first. No speech is produced. - Typical value: digest, or
none. - Why not in the id: some browsers fill the list late.
audio_latency
- Reads:
AudioContext.baseLatency, and closes the context at once. Nothing is played. - Typical value: a number of seconds such as
0.005333. - Support: read only in Safari and on Android. Elsewhere creating an
AudioContextoutside a user gesture prints an autoplay warning in the console, so it reportsunavailablewith ruleIdskipped. - Why not in the id: it follows the audio output device.
canvas_readback
- Reads: paints two known colours into a 1x1 canvas and reads them back
with
getImageData(). - Typical value:
exact, oralteredwhen something adds noise to canvas reads. - Why not in the id: it is a diagnostic about the browser’s settings, not about the device.
content_blockers
- Reads: adds eight 1x1 bait elements with ad-like class names and ids
(for example
ad-banner,adsbox) in an off-screen container, waits 50 ms, and lists the baits that cosmetic filters hid. Everything is removed afterwards; no request is made. - Typical value:
none, orad_banner,ad_box,banner_ad. - Why not in the id: users turn blockers on and off per site.
Left out on purpose
WebRTC (even without addresses), media device lists, network information,
battery, geolocation, storage quota and user-agent parsing are not
collected. The reasons are in the roadmap in the library source (docs/ROADMAP.md).