By default, about 1 in 100 page loads that use WhiskerJS send one small report to the WhiskerJS project saying which components worked in that browser and which did not. It helps find signals that break in a new browser release. It never contains what was measured: no values, no digests, no ids. Any one of four switches turns it off. The server that receives it keeps only daily counts and never stores IP addresses or user agents.
Turn it off
Any one is enough:
// 1. For the agent: no report from any collect() of this agent.
const agent = await load({ diagnostics: false });
// 2. For one call.
await agent.collect({ diagnostics: false });
<!-- 3. For the whole page, in the HTML (content is case-insensitive). -->
<meta name="whiskerjs-diagnostics" content="off">
- The visitor’s browser:
navigator.globalPrivacyControl === true(Global Privacy Control), or Do Not Track set (navigator.doNotTrack,window.doNotTrackornavigator.msDoNotTrackis'1','yes'or1). If these cannot be read, diagnostics are treated as off.
With diagnostics off, the library makes no network request at all. The live demo turns diagnostics off.
Other settings:
| Option | Default | Meaning |
|---|---|---|
load({ diagnostics }) |
true |
false turns it off for the agent |
load({ diagnosticsEndpoint }) |
DIAGNOSTICS_ENDPOINT, https://diagnostics.whiskerjs.com/v1/report |
send to your own endpoint instead; must start with https://, anything else turns it off |
load({ diagnosticsSampleRate }) |
DEFAULT_DIAGNOSTICS_SAMPLE_RATE, 0.01 |
share of page loads that send, clamped to 0 (never) to 1 (every page load, still at most once) |
collect({ diagnostics }) |
sends when sampled | false sends nothing for that call |
The report
The whole body, field by field. Nothing else is sent:
| Field | Example | Meaning |
|---|---|---|
version |
"0.1.0" |
library version |
signalsVersion |
"11" |
signal set version |
idVersion |
"2" |
id derivation version |
browser_class |
"chromium" |
the value of the browser_class component (other when the component is not ok) |
durationMs |
412 |
how long the collection took, whole milliseconds, at most 60000 |
components |
{"audio":{"status":"ok"},"canvas":{"status":"blocked","ruleId":"threw"}} |
every component, sorted: only its status and, when it has one, its ruleId |
This is exactly the receiver’s schema: it answers 400 to any other field, a missing field, a value outside these shapes or a body over 8 KB. A result that would not fit (for example a version string the schema does not allow) is not sent at all.
diagnosticsReport(result) (exported) returns exactly this object for a
given result, or null when nothing would be sent for it, so you can see
what would be sent.
Never sent: component values or digests, the visitorId, the structuredId, integrity flags, the binding nonce, timestamp or digest, the page URL, the referrer, cookies, storage, or anything else from the page. The result itself is never sent by the library; your site sends it with its own request (see Integration).
How it is sent
- When: after
collect()has its result and after youronResulthook. It is never awaited, never retried, and never delays or changescollect(); any failure is ignored. - How often: the first
collect()on a page makes the sampling decision once (for the window object, in memory; nothing is stored). Later calls on the same page, from any agent, never send. A call withdiagnostics: false, an opt-out or an invalid endpoint makes no decision. - Sampling: the window’s
Math.random()against the sample rate. - Request: one
fetchwithmethod: 'POST',mode: 'no-cors',credentials: 'omit'(no cookies),referrerPolicy: 'no-referrer'(no URL or referrer),cache: 'no-store',keepalive: trueand atext/plain;charset=UTF-8body (so no CORS preflight).navigator.sendBeaconis not used because it always sends cookies.
What the browser adds on its own
Like every request a browser makes, the report request carries data the library cannot remove:
- the visitor’s IP address (the server needs it to answer) and the
browser’s own request headers, including the
User-Agentand theOrigin(the page’s scheme and host, without the path); - when the endpoint is down or unreachable (an outage, a blocker, a network error), the browser itself may print one line about the failed request in the developer console and show it in the network log. The library never writes to the console; this line comes from the browser;
- a page Content Security Policy without the endpoint in
connect-srcblocks the request and may report the violation to the page’s own reporting endpoint. A site that keeps diagnostics on addshttps://diagnostics.whiskerjs.comtoconnect-src; with diagnostics off nothing beyondscript-srcis needed.
What the server keeps
The receiver at diagnostics.whiskerjs.com is run by the WhiskerJS project.
It never stores the IP address or the user agent, nor any other header,
URL or referrer, and it does not keep the report body. It only adds the
report to daily counts (per version, browser class, component, status and
ruleId, and a histogram of durationMs), kept 400 days. The IP address is
used only in memory, as the key of a rate limiter, through a salted hash
whose salt is replaced every day; there are no access logs.
If your privacy notice does not cover this, turn diagnostics off. See Privacy and what a privacy notice should say.
How the tests keep it honest
- The compiled-source scan allows a network API only in
src/diagnostics.tsand checks that file sends no credentials and no referrer. - The fake browser pins
Math.randomoutside the sample, so every other test stays deterministic and sends nothing; the one test that runs against the real Node globals passesdiagnostics: false. - Dedicated tests cover the exact payload, the unsampled case, once per page load across agents, every off switch, a sample rate of 0, a non-https endpoint, a custom endpoint and rate, and failing, hanging and throwing sends.
- A schema test keeps a copy of the receiver’s rules and checks that every browser class, status, component name and ruleId the library can emit is accepted, that a report for each browser class and a worst case (every component with a 32-character ruleId) fit, and that a result outside the schema sends nothing.
- The real-browser tests (Chromium, Firefox in three settings, WebKit) run twice in CI: with diagnostics on and sampling forced to every page load, the only request allowed beyond the page’s own files is exactly one diagnostics report per page load, checked against the receiver’s schema (the test answers it; it never reaches the real endpoint); with diagnostics off, the job asserts zero requests beyond the page’s own files.