Signals
A glassprint identification carries the verdict and the signals that informed it. The signals are additive data — you feed them into your existing risk engine. They are explicitly not a verdict to act on blindly: a high suspect score is input to your decision, not the decision.
The nine client probes
Each probe reads one signal behind the consent gate and declares an entropy axis so the server can de-duplicate correlated signals. Every probe degrades to null outside a real browser.
| probe | axis | What it reads |
|---|---|---|
canvas.pixelHash | gpu | OffscreenCanvas 2D render (sub-pixel text, emoji, .notdef tofu, gradient, alpha), hashed. Read twice to set a farble-suspected flag (Brave/Tor) — flagged, never treated as a bot. |
webgl.renderer | gpu | Unmasked WebGL vendor/renderer + fragment-shader precision. Untrusted — the server verifies the renderer string. |
audio.fp | cpu-fpu | OfflineAudioContext oscillator → compressor; a sum/peak ratio that is more farble-invariant than the absolute sum. |
fonts.measured | os | measureText width-delta enumeration of probe fonts vs baselines; the server computes Jaccard/MinHash similarity. |
ua.context | os | UA-CH high entropy: platform/arch/browser MAJOR version, timezone, screen, dpr, cores, memory, language. Minor/build version is deliberately NOT collected — it is drift evidence only, so an auto-update never severs a visitor. |
webgpu.adapter | gpu | navigator.gpu adapter vendor/architecture/device. Untrusted, server-verified; survives a blocked WebGL string. |
math.libm | cpu-fpu | Last-ulp of 12 transcendental functions at edge arguments. A math-vs-OS contradiction is a strong server-side spoof tell. |
codec.support | os | MediaCapabilities over H.264/VP9/AV1/HEVC + AAC/Opus/Vorbis; supported/smooth/power-efficient bits. |
env.automation | os | navigator.webdriver and /headless/ in the UA — PUBLIC facts only. Interpretation lives server-side; it is never an identity key. |
mems sensor axis (motion/orientation) and the cross-browser precise-device-identification purpose are on the type surface but are roadmap, not shipped today.The JA4 server tier
On top of the client probes, the server adds a network-tier signal that JavaScript can never see: the JA4 / HTTP2 / TCPfingerprint, captured at your edge. It is honored only behind the CIDR trust boundary — from an untrusted peer it is stripped and the topology drops, which is what feeds the verdict's topology cap.
The SmartSignals dictionary
The response carries an explicit SmartSignals object — additive data, never a verdict the customer must act on blindly:
| field | Type | Meaning |
|---|---|---|
bot | boolean | Automation/headless evidence present, reconciled with the final class after any human override. |
incognito | boolean | Always false today — it lands with more signals (roadmap), and we will not emit a value we cannot stand behind. |
tampered | boolean | Genuine cross-signal contradictions, AND only when no legitimate privacy defense explains them — deliberately NOT flagged for Brave/Tor. |
inconsistency | number | The count of cross-signal contradictions (GPU↔OS impossibilities, empty renderer, etc.). |
suspectScore — additive risk data
Alongside SmartSignals, the response carries a suspectScore in [0, 1]: the inconsistency count plus per-tell weights (software GPU 0.35, webdriver 0.45, headless-UA 0.45), clamped. A recognised privacy defense pulls it down to ≤0.2.
suspectScore is additive DATA, not a verdict to act on blindly. Use it as one input to your own risk decision — do not auto-block on it. The class already encodes the conservative read; the score lets you tune your own threshold.Confidence is identity, not bot-ness
One subtlety worth internalising: the response confidence is the identity-resolution confidence (how sure we are this is the same device), not bot-ness. Bot-ness is conveyed separately via class + suspectScore + smartSignals.
const r = await gp.identify({ tag: 'signup' });
const risk = myRiskEngine({
visitorId: r.visitorId,
classGuess: r.class, // human | declared_agent | undeclared_automation | unknown
suspect: r.suspectScore, // additive — your threshold, not ours
signals: r.smartSignals, // { bot, incognito, tampered, inconsistency }
needsAttest: r.needsAttestation, // low-entropy device → route to step-up
});