Questa traduzione è generata automaticamente e in attesa di revisione.Passa all'inglese
In questa pagina

Install & Browser Runtime

Find your snippet in the Backoffice → Integrations & API → Script & Consent (/integrations/script-consent). Pick the domain and the page generates the tags with that domain’s Site Key and the current collector build, plus the consent mode, protected actions, and a verification checklist. This page is the public walkthrough of the same content: what to install, in which order, and how to check that it works. Every Site Key on this page is a placeholder (pk_prod_your_site_key_here); never copy a key, hash, or build name from a document.

Use the developer overview for the dated release check and the maintained browser fixture. Other docs pages link here instead of repeating install steps.

Current public surface

The public collector loader is served from the Noxtica collector origin, https://collect.noxtica.com unless your account has a provisioned collector host. This page calls that origin “your collector host”. No public npm package, framework wrapper, native SDK, or cross-platform wrapper passed the release check recorded in the developer overview, so those surfaces are omitted rather than inferred from private inventory.

The collector is two scripts. Load the core on every page; load the extensions only when your consent mode allows it.

  • The core (noxtica-collector-core.js) needs no consent. It obtains the session token, submits the minimal security check, answers server-requested challenges, offers the consent API and the extensions loader, and runs protected actions. In a consent-gated mode it stores nothing on the visitor’s device.
  • The extensions (noxtica-collector-extensions.js) need consent. They carry persistent device recognition, the full device fingerprint, analytics, real-user monitoring and console logs, session replay, and behavioral signals. Each module still runs only when its category is granted.
<script
	src="https://collect.noxtica.com/collector/noxtica-collector-core.js"
	data-site-key="pk_prod_your_site_key_here"
	data-auto-init
	data-auto-check-once
	async
></script>
AttributePurpose
data-site-keyThe public Site Key of the exact origin. Keys are origin-bound: another origin receives 403 origin_mismatch.
data-auto-initCreates the client and assigns window.noxticaClient.
data-auto-check-onceRuns the cache-aware check (checkOnce()) once per page.
data-extensionsOptional. "auto" makes the core load the extensions itself; any other value keeps that off.
asyncThe core never needs to block rendering.

Load the extensions in one of three ways:

  • add data-extensions="auto" to the core tag: the core loads them immediately in not_required mode and on the first granted category in the api and cookie modes;
  • call await window.noxticaClient.loadExtensions() after your page grants consent (see Loading extensions);
  • inject <script src="https://collect.noxtica.com/collector/noxtica-collector-extensions.js" async></script> yourself after consent. It carries no Site Key and attaches to the core already on the page.

Loading the extensions never grants consent by itself. You can also switch Auto-load extensions on per domain in Script & Consent. In not_required mode that loads the extensions for every visitor, including those who declined your own banner, so treat it as a decision about your banner, not a convenience.

Existing installs. The legacy URLs https://collect.noxtica.com/collector/noxtica-collector.js and /collector/noxtica.js now serve the core and load the extensions automatically: immediately in not_required mode, after the first grant in the gated modes. Set data-extensions to any other value to opt out. The original combined bundle is deprecated and no longer offered; new installs use the core tag above.

Device-analysis engines. The KHAN and Obsidian engines are split the same way (khan-core and obsidian-core, then the matching extensions) and load automatically with the core and the extensions. You never add their tags.

Stable or pinned snippets

Script & Consent generates two variants of each tag from the collector’s live manifest, https://collect.noxtica.com/collector/asset-manifest.json.

  • Stable (default). The unhashed URL always serves the current build and carries no integrity attribute, so the tag never needs updating.
  • Pinned with Subresource Integrity. Use it only if your security policy requires an integrity attribute. A pinned tag references one exact content-hashed build:
<script
	src="https://collect.noxtica.com/collector/noxtica-collector-core.<hash>.js"
	integrity="sha384-<from the manifest>"
	crossorigin="anonymous"
	data-site-key="pk_prod_your_site_key_here"
	data-auto-init
	data-auto-check-once
	async
></script>

Build it from the manifest: assets["noxtica-collector-core.js"] is the file name under /collector/ and integrity["noxtica-collector-core.js"] is the integrity value. The extensions use the keys noxtica-collector-extensions.js. A pinned core loads the extensions with their own integrity hash, so you do not pin the extensions separately.

How long a pin lasts. The hashed core and extensions bundles of the last three previous releases keep resolving, byte-identical and immutable, and are listed in previous[] of the manifest. Copy the pinned tag again within three releases; after that the pinned file stops being served and the browser blocks it. Hashed files are never a substitute for the manifest: do not copy a hash from a document, including this one.

The recorder head is the exception. The early replay loader (noxtica-recorder-head.js) is not retained: its hashed URL stops resolving on the next recorder build, so the stable tag in the next section is the default.

Replay recorder head

Session replay can start before the page paints. The recorder head is a small synchronous preloader that buffers a bounded early window in memory and has no transport. The asynchronous core drains it only after the configured replay admission checks allow capture; otherwise the queue is discarded. Sites that do not use replay may omit it.

<script src="https://collect.noxtica.com/collector/noxtica-recorder-head.js" crossorigin="anonymous" data-cfasync="false"></script>
  • It must be the first executable script in <head>, before anything else that runs.
  • Load it synchronously: never async, defer, or type="module".
  • data-cfasync="false" stops Cloudflare Rocket Loader from deferring it.
  • It needs an armed replay policy for the domain (Backoffice → Session Replay) and a consent flow that calls setConsent with the replay category.
  • A pinned variant comes only from the live manifest key noxtica-recorder-head.js; keep crossorigin="anonymous" and data-cfasync="false".

Content Security Policy

If your site sends a Content Security Policy, add only what the collector needs, and merge the sources into your own lists; do not paste a vendor policy over yours. Hosts below are your collector host (collect.noxtica.com by default) and the ingest host (ingest.noxtica.com, derived automatically from the default collector host).

DirectiveAllowWhy
script-srcyour collector host and 'wasm-unsafe-eval'Loads the core, extensions, and engine loaders. 'wasm-unsafe-eval' lets the KHAN and Obsidian engines compile WebAssembly; it does not allow eval().
worker-srcblob: (keep your own entries, such as 'self')The engines start workers from blob: URLs. A worker script must be same-origin, so it cannot be served from the collector host.
connect-srcyour collector host and the ingest host (only if your page declares one)Token and submit calls go to the collector host. Analytics, RUM, console logs, and replay use the ingest host and fail over to the collector once, then stay there; a policy with only one of the two breaks half the traffic.

If your domain uses server-requested challenges, also allow Cloudflare Turnstile as its documentation describes (https://challenges.cloudflare.com). The Script & Consent CSP & storage tab prints the same lines with your own hosts filled in.

Content-Security-Policy:
  script-src 'self' https://collect.noxtica.com 'wasm-unsafe-eval';
  worker-src 'self' blob:;
  connect-src 'self' https://collect.noxtica.com https://ingest.noxtica.com;
  • 'wasm-unsafe-eval' is needed. Without it the engines cannot start; the SDK fails open, collection continues with reduced evidence, and a structured error is reported.
  • 'unsafe-eval' is not needed. Noxtica does not need 'unsafe-eval', and a policy without it produces no eval violation from Noxtica. The only Noxtica code that ever called new Function(...) was an optional KHAN diagnostic lane in the extensions runtime: a one-line capability probe, new Function('return 0'), and nine diagnostic programs. No score, verdict, or device identifier depends on it. The lane runs only while the signed token arms it, and from release r32 (2026-10-24) it is disarmed for every domain, so the runtime no longer evaluates dynamic code. If a site still sees eval reports, it is on an older token or a cached copy: an enforcing script-src reports one violation (blocked URI eval, source …/collector/noxtica/noxtica-khan.js) and a report-only policy about ten per consented visit. Reload with a fresh page load to get a current token; no policy change is needed. Do not add 'unsafe-eval' for Noxtica.
  • chrome-extension: reports are probe noise. On Chromium, with the behavioral category granted, the extension probe requests a few chrome-extension://… resources through script tags. An enforcing script-src can report them. The probe reads a blocked or missing resource as “not installed”, so do not allow chrome-extension: for Noxtica.
  • Hash or nonce next to 'unsafe-inline'. Browsers that see a hash or nonce ignore 'unsafe-inline', which breaks every other inline script. If your policy relies on 'unsafe-inline', do not add a hash or nonce for the inline listener without a full nonce migration; if you already use nonces, give the inline listener its nonce.
  • Probe periodically. A permissive policy hides what it allows: with 'unsafe-inline' and 'unsafe-eval' listed, report-only telemetry sees no inline or eval use. Send a stricter policy as a second, report-only header for a day before you tighten the enforcing one, and read the reports. It changes nothing on the page:
Content-Security-Policy-Report-Only: script-src 'self' https://collect.noxtica.com; worker-src 'self'; connect-src 'self' https://collect.noxtica.com https://ingest.noxtica.com; report-uri /csp-report

Consent modes let a domain split the collector into a consent-free core and consent-gated categories. New domains start in api mode: the page check is skipped (noxtica:collected carries skipped: 'consent_required') and nothing persistent is written until your page calls setConsent. Domains that already existed keep the mode they have, so an existing install changes nothing. not_required remains an explicit choice that keeps the behavior from before consent modes existed.

Choosing a mode

Set the mode per domain in Backoffice → Integrations & API → Script & Consent (the Consent & extensions tab), stored as the domain’s client_consent_mode. The collector reads it when it issues the browser token.

ModeBehavior
not_requiredNot a consent gate. Domains created before the api default and never changed keep this mode, and you can choose it explicitly. Collection runs as configured today. An explicit denied from your page is still honored for that category.
apiThe default for new domains. Every category starts denied. Your page grants or denies categories with client.setConsent(...), usually from your consent management platform (CMP).
cookie:<name>The collector reads the first-party cookie <name>. Present and non-empty means every category is granted; absent or empty means every category is denied.
cookie:<name>=<value>The collector reads cookie <name>, URI-decodes it, and grants every category when the decoded value contains <value>. Otherwise every category is denied. This matches CMP strings, for example cookie:OptanonConsent=C0002:1.

Cookie names may use letters, digits, _, ., and -; values may additionally use :, %, and +. Backoffice rejects anything else. A value that cannot be parsed is treated as not_required, so the domain keeps its current behavior.

not_required is not a consent gate. The persistent device identifier is written unless your page set deviceId to denied, and nothing waits for your banner. The extensions write it, so in this mode it applies from the moment they load: right away with data-extensions="auto", the legacy URLs, or Auto-load extensions. The core alone never writes persistent storage. Pick api or a cookie mode when your visitors must decide first.

Declaring the mode on the loader (optional)

The domain setting arrives with the first token. If your consent decision reaches the page by API, also declare the mode on the core tag so the client is strict from its first line, before that token returns:

<script
	src="https://collect.noxtica.com/collector/noxtica-collector-core.js"
	data-site-key="pk_prod_your_site_key_here"
	data-consent-mode="api"
	data-auto-init
	data-auto-check-once
	async
></script>

Accepted forms are data-consent-mode="api", data-consent-mode="cookie:name" or data-consent-mode="cookie:name=value", and the shorthand data-consent-cookie="name" or data-consent-cookie="name=value". Programmatic clients pass the same value:

const client = NoxticaCollector.createClient({ siteKey: 'pk_prod_your_site_key_here', consentMode: 'api' });

A declaration can only make the client stricter. The domain setting stays authoritative for server enforcement, and a tag can never relax it. Without the declaration, a call such as setConsent made in the window before the token is announced with mode: "not_required". Up to SDK 3.8.0 the same event then reports api only once the token arrives; from SDK 3.8.1 the client dispatches a second noxtica:consent-changed with mode: "api" when the token resolves, and getConsent() and the event detail carry modeResolved (false until then). If your bridge ever sends a partial object, or calls loadExtensions() before setConsent, declare the mode.

Core and extensions in a gated mode

In the api and cookie modes, keep the core on every page and load the extensions only after the visitor accepts (see Install). With the api mode, grant the categories and then load the extensions:

const client = window.noxticaClient;
client.setConsent({ deviceId: 'granted', analytics: 'granted', replay: 'granted', behavioral: 'granted' });
await client.loadExtensions(); // resolves { ok: true } once the extensions are active

Each value is the string 'granted' or 'denied'; any other value, including true or false, throws a TypeError. Adding data-extensions="auto" to the core tag makes the core load the extensions on the first granted category instead.

With data-auto-init, the client is available as window.noxticaClient. Programmatic clients use the object returned by createClient.

  • client.setConsent({ analytics, replay, deviceId, behavioral }) sets each listed category to 'granted' or 'denied'. Omitted keys keep their current state.
  • client.setConsent('granted' | 'denied') is the legacy form. It changes analytics only.
  • client.setReplayConsent(true | false) changes replay only.
  • client.getConsent() returns the current state: { mode, analytics, replay, deviceId, behavioral, privacySignal }, where privacySignal is 'gpc', 'dnt' or null.

Example wiring with a generic CMP callback (OneTrust- or Cookiebot-style). Map your CMP’s purposes to Noxtica categories as your privacy team decides:

const client = window.noxticaClient;
const state = (allowed) => (allowed ? 'granted' : 'denied');

cmp.onConsentChanged((choices) => {
	client.setConsent({
		analytics: state(choices.statistics),
		replay: state(choices.statistics),
		deviceId: state(choices.preferences),
		behavioral: state(choices.marketing),
	});
});

In cookie modes the collector reads the cookie when it starts and again on visibilitychange and focus, so a decision made in your banner applies when the visitor returns to the tab. A grant applies to all categories at once. A missing or non-matching cookie denies all categories and purges the persistent device stores.

The authoritative mode lives in the domain’s tenant settings, not in your page code, so a settings change can alter behavior without any deploy on your side. Check what the browser actually receives:

  • window.noxticaClient.getConsent().mode reports the effective mode: not_required, api, cookie:<name>[=<value>], or gcm. Read it after the first noxtica:collected event, because until the token arrives the client does not know the domain’s mode.
  • collectorConfig.consent in the POST /client/token response (DevTools → Network) carries the server-side mode.

Make this a release check on the live page, with the banner still unanswered:

// Expect your configured mode ('api' here). 'not_required' means the domain setting changed or the wrong domain is selected.
document.addEventListener('noxtica:collected', () => {
	console.assert(window.noxticaClient.getConsent().mode === 'api', 'Noxtica consent mode drifted');
});

With the mode api and nothing granted, you should see only the core script and POST /client/token, a sessionStorage token, and a noxtica:collected event with skipped: 'consent_required'. Before any decision every category must read unknown or denied, and Object.keys(localStorage).filter((k) => k.startsWith('nox_')) must be empty. Anything in localStorage, IndexedDB, or cookies before the visitor answers is a finding.

Events and timing

Attach your listeners before the loader can run, and treat events as signals, not as data sources.

  • Listen early. Put the noxtica:collected listener in an inline <head> script before the core tag. checkOnce() announces a visit once and does not re-collect inside its TTL, and an event that already fired is not replayed.
  • Deliver consent on noxtica:ready (SDK 3.6.0 and later), which fires once per tag after window.noxticaClient exists. Back it with a bounded poll for older cores, and let the latest choice win.
  • Never trust event.detail.client. Any script can dispatch a forged noxtica:ready. Use the event as a signal and read window.noxticaClient, which the core assigns before it dispatches.
  • Prefer deciding consent in a noxtica:ready handler, not in the pre-init queue (noxtica('setConsent', …)). Before SDK 3.8.1 the queue replays every command in order, so a grant followed by a withdrawal still runs loadExtensions(). From SDK 3.8.1 queued consent updates are folded into one (the later key wins), a queued loadExtensions is dropped when all four categories end denied (noxtica:extensions-failed with consent_missing), and one noxtica:consent-changed fires. Use the queue for one-time declarations either way.
  • Decide “is Noxtica on this page” by host or build configuration, not by a DOM attribute: a client-side locale switch or framework remount can clear attributes on <html>.
<head>
	<script>
		document.addEventListener('noxtica:collected', (event) => {
			const result = event.detail;
			if (result.skipped === 'consent_required') return; // not an error: no device ID yet
			// Use result.fingerprintId only as a lookup handoff to your backend.
		});
	</script>
	<script src="https://collect.noxtica.com/collector/noxtica-collector-core.js" data-site-key="pk_prod_your_site_key_here" data-auto-init data-auto-check-once async></script>
</head>
// Run when your consent choice is known; calls whenNoxticaReady once the client exists.
function whenNoxticaReady(callback, { timeoutMs = 15000 } = {}) {
	if (window.noxticaClient) return callback(window.noxticaClient);
	const started = Date.now();
	const poll = setInterval(() => {
		if (window.noxticaClient) { clearInterval(poll); callback(window.noxticaClient); }
		else if (Date.now() - started > timeoutMs) clearInterval(poll); // fail open: no client, no collection
	}, 250);
	document.addEventListener('noxtica:ready', () => { clearInterval(poll); if (window.noxticaClient) callback(window.noxticaClient); }, { once: true });
}
Eventevent.detailWhen
noxtica:ready{ client }data-auto-init created the client (SDK 3.6.0+). The first check has not started.
noxtica:collectedthe check result, or { success: false, skipped: 'consent_required', consentMode }After each check, and when a check is deferred until consent.
noxtica:cache-hitthe same result with fromCache: trueRight after noxtica:collected when the TTL served the stored result.
noxtica:consent-changedthe getConsent() snapshotA category really changed.
noxtica:extensions-loaded{ message: 'attached', ok: true, loaded }The extensions took over the client.
noxtica:extensions-failed{ message: 'not-loaded', ok: false, reason, code }A load attempt failed. A later loadExtensions() retries.
noxtica:error{ source, message }A failure the SDK reported; also stored in window.noxticaLastError.

The extensions-* event details are objects from SDK 3.8.0 and strings before it.

Loading extensions and its result

loadExtensions() never rejects. From SDK 3.8.0 it resolves { ok: true, loaded } on success and { ok: false, reason, code } on failure; up to 3.7.0 it resolved a boolean. An object is always truthy, so test the result, not its existence. Failures do not throw, so a retry guard that only clears on rejection never clears:

let extensionsRequested = false;
async function requestExtensions() {
	if (extensionsRequested) return;
	extensionsRequested = true;
	const res = await window.noxticaClient.loadExtensions();
	const ok = res === true || res?.ok === true; // boolean up to 3.7, object from 3.8
	if (!ok) extensionsRequested = false; // allow a retry on the next consent signal
}
reasonProduced when
consent_missingYou passed { requireConsent: true } in a gated mode and no category is granted. Nothing is fetched.
csp_blockedThe browser reported an enforced CSP violation for the script URL.
integrity_mismatchA pinned file’s bytes differ from its integrity value.
networkAn HTTP error or no response for the script (http_<status>, fetch_failed).
timeoutNeither a load nor an error event within 20 seconds. A later call retries.
already_loadedA <script> for the extensions URL is already in the document but nothing registered.
unknownEverything else, such as a build mismatch.

An SRI-pinned core never falls back to unpinned code. An unpinned core falls back from the hashed file to the stable name. Treat reason as a hint for logging and ok as the contract.

These parts run in every mode because they protect the page and its visitors:

  • browser token issuance;
  • /client/submit with the minimal sealed-runtime fingerprint;
  • anti-automation counters (event counts and timing, no coordinates or key values);
  • challenges that the server requests.

Until deviceId is granted, the device identifier stays in memory and sessionStorage and is not written to localStorage, IndexedDB, or cookies.

CategoryWhat a grant enables
analyticsReal-user monitoring (RUM), RUM batches and error detail, console log capture, the interaction tracker, and the durable client event buffer.
replaySession replay recording and replay chunk upload.
deviceIdPersistent device storage (localStorage, IndexedDB, first-party cookie) and the server’s persistent device-recognition token.
behavioralBehavioral biometrics, pointer and touch tracks, geolocation, cookie contents, favicon supercookie detection, the extension probe, and the WebRTC probe.

A category that is granted still depends on the tenant and domain features you have enabled; consent never switches on a feature your configuration leaves off.

Server enforcement

When a domain’s mode is anything other than not_required, the collector enforces consent on its side too. The loader adds an nxc query parameter naming the granted categories to each collector request; do not strip it in proxies or rewrite rules. The collector then:

  • accepts and discards analytics payloads without analytics consent;
  • accepts and discards replay payloads without replay consent;
  • removes behavioral fields from /client/submit without behavioral consent;
  • skips the persistent device-recognition token without deviceId consent.

A request without the parameter, for example from an older cached loader, counts as nothing granted. Discarded payloads still receive a normal success response, so the page never sees an error. Domains on not_required are not affected.

Persistent device recognition switched off

If persistent_device_recognition_enabled is false for the domain, the collector never persists the device identifier in gated modes, even after deviceId is granted.

This table is informational and not legal advice; consult your DPO/counsel. Classification depends on your jurisdiction, purposes, and configuration.

FeatureData accessedStored on device?PurposeSuggested categoryTypical legal basis note
Browser tokenSite Key; the server sees IP address and request headerssessionStorage token cache for the sessionSecuritycoreSecurity storage is often treated as strictly necessary under ePrivacy Art. 5(3); GDPR Art. 6(1)(f), fraud prevention (Recital 47).
Minimal sealed fingerprint (submit)Browser and device characteristics read inside the sealed runtime; proof of worksessionStorage status flagFraud preventioncoreReading device information is access under Art. 5(3); whether the strictly-necessary exemption applies needs assessment. GDPR Art. 6(1)(f).
Anti-automation countersEvent counts, trusted-event flags, timing gaps, pressure; no coordinates or keysNoBot detectioncoreGDPR Art. 6(1)(f); confirm the Art. 5(3) position with counsel.
Server-requested challengeLoads Cloudflare Turnstile when the server asks for itNo by the collector; see Cloudflare’s Turnstile documentationSecuritycoreSecurity of the requested service; list Cloudflare as a processor or recipient.
Network and edge signals (server side)IP address, TLS and HTTP characteristics, rate limitsNoFraud preventioncoreNo terminal access, so Art. 5(3) does not apply; GDPR Art. 6(1)(f).
Cross-tab coordinationLock and refresh timestampsYes, small localStorage keys, no identifierOperationcoreFunctional state; strictly-necessary exemption commonly argued.
Session device identifierDevice identifier held for the current sessionMemory and sessionStorageFraud preventioncoreSession-scoped storage; Art. 5(3) analysis still applies.
Persistent device identifierDevice identifier and an integrity canaryYes: localStorage, IndexedDB, first-party cookie (up to 400 days)Cross-session recognitiondeviceIdStorage under Art. 5(3) usually needs consent unless strictly necessary; GDPR Art. 6(1)(a) or (f).
RUM, RUM batches, error detailPerformance and resource timing, redacted URLs, errorsNoAnalyticsanalyticsArt. 5(3) consent is typical; some regulators exempt narrow audience measurement.
Console log capturePage console outputNoAnalytics and supportanalyticsConsent is typical; logs may contain personal data your page writes.
Interaction trackerClicks with target element type, scroll dwell, page viewsNoAnalyticsanalyticsArt. 5(3) consent is typical.
Durable client bufferQueued analytics eventsYes, IndexedDBAnalytics deliveryanalyticsStorage under Art. 5(3); follows the analytics purpose.
Session replay and replay preloaderPage structure, masked inputs, network and console metadataPreloader: memory only; recorder: sessionStorage tab identifierUser experience and supportreplayConsent is typical (Art. 5(3) and GDPR Art. 6(1)(a)); masking reduces the risk of capturing special-category data.
Behavioral biometricsClick, scroll, and key timingNoFraud preventionbehavioralCan be biometric data under GDPR Art. 4(14) and Art. 9 when used to identify a person; explicit consent, Art. 9(2)(a). US: BIPA.
Pointer and touch tracksPointer and touch coordinatesNoFraud preventionbehavioralGDPR Art. 9 risk as above; US: BIPA.
GeolocationLatitude and longitude, only when the browser permission is already grantedNoFraud preventionbehavioralPrecise location is high-risk data; browser permission is not a GDPR legal basis.
Cookie contentsdocument.cookieNoFraud preventionbehavioralAccess under Art. 5(3); cookies may hold other vendors’ identifiers.
Favicon supercookie detectionSame-origin favicon cacheNoFraud preventionbehavioralAccess under Art. 5(3).
Extension probePresence of browser extensionsNoFraud preventionbehavioralAccess under Art. 5(3); extensions can reveal sensitive traits (Art. 9 risk).
WebRTC probeICE candidates through a Google STUN server; public IP addresses are HMAC-hashedNoVPN and proxy detectionbehavioralThird-party STUN contact; GDPR Art. 6(1)(f) with a balancing test.

Live assist is loaded separately and asks for its own consent; it is outside these categories.

Protected actions

Protected actions keep bot and fraud protection on the steps attackers target, even for visitors who have not consented. Examples are login, signup, checkout, kyc, and password_reset. When one runs, the core executes the KHAN and Obsidian cores in minimal mode: they read device and browser characteristics only, store nothing on the device, and run no behavioral, replay, or analytics collection. Ordinary page views are never exempt.

1. Declare each action per domain in Script & Consent (Protected actions tab): a name (lowercase, up to 32 characters) and exactly one trigger, matched by path glob (/login, /checkout/**), by CSS selector of a form or button, or by a window CustomEvent name. A domain has at most 16 actions. Avoid catch-all paths such as /**: they would run the minimal check on every page view.

2. Trigger from the page, either on the form or in code:

<form id="login" data-nox-protect="login" method="post" action="/session">…</form>
const { requestId } = await window.noxticaClient.protect('login');

On a native, navigating form submit the core holds the submit for at most 6 seconds and adds a hidden nox_request_id field; a slow or failed check never blocks the visitor. For script-submitted forms see Single-page apps and frameworks.

3. Verify on your server. Send the requestId to your backend with the protected request and redeem it from the backend, never from the browser. The call needs a Server API key (sk_…) with the read:device scope. The id is single-use inside a 300-second window.

curl -X POST "https://collect.noxtica.com/v1/protected-actions/verify" \
  -H "Authorization: Bearer sk_prod_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{"requestId":"REQUEST_ID","action":"login","expectedDomain":"www.example.com"}'
StatusMeaning
200Redeemed. The body carries fingerprintId, verdict, score, and confidence.
409already_consumed: a replay inside the window. Refuse.
404Unknown, expired (over 300 seconds), or another tenant’s id.
422action_mismatch or domain_mismatch. The receipt is not consumed.
401Missing or invalid key, or a key without the read:device scope.

A missing requestId (timeout, blocked script) means “no client signal”, not a pass: apply your own step-up. Use the fingerprintId from the verify response as the authoritative device identifier for that action, not an identifier the browser sent in a header. Up to SDK 3.8.0, two noxtica:collected events with different fingerprintId values could fire on one page (a minimal check by the core and a full one by the extensions when consent arrived while the token was still loading); from SDK 3.8.1 one page load produces one submit and one noxtica:collected. Identifiers can still differ between runtimes, so a client-sent value is not a source of truth.

Single-page apps and frameworks

  • Load the core once, in the app shell, never per route or per component. A route change does not need a new tag.
  • data-auto-check-once is TTL-cached. checkOnce() respects the configured interval and announces a visit once, so a client-side navigation does not re-collect. Listen for noxtica:collected before the loader runs, as described in Events and timing.
  • Script-submitted forms (React, SPA, fetch) cannot use the hidden nox_request_id field, because your own handler calls preventDefault(). A capture-phase listener starts the protected check before your handler and publishes the result on form.dataset.noxRequestId. Read it synchronously at the top of the handler, and call protect() synchronously to join the running check if it is not there yet:
async function onSubmit(event) {
	event.preventDefault();
	const form = event.currentTarget;
	// Both lines run before the first await: the id is withdrawn when this handler returns.
	const ready = form.dataset.noxRequestId;
	const pending = ready ? null : window.noxticaClient.protect('login', { timeoutMs: 6000 });
	const requestId = ready ?? (await pending).requestId; // undefined on timeout or blocked script
	await fetch('/api/login', {
		method: 'POST',
		headers: { 'Content-Type': 'application/json', 'X-Request-Id': requestId ?? '' },
		body: JSON.stringify(payload),
	});
}

The id is attempt-scoped because it is single-use on the server: a retry after a wrong password runs a fresh check. await noxticaClient.protect(name) alone, without data-nox-protect, is the right tool for buttons, route changes, and flows without a <form>.

  • Angular and Zone.js. The collector’s own listeners, observers, and timers resolve the native functions that Zone.js leaves on the prototypes, so they do not trigger change detection. The session-replay recorder’s internal listeners are third-party and are not patchable. If that matters, load the core outside the Angular zone:
constructor(private zone: NgZone) {}
ngOnInit() {
	this.zone.runOutsideAngular(() => {
		const s = document.createElement('script');
		s.src = 'https://collect.noxtica.com/collector/noxtica-collector-core.js';
		s.dataset.siteKey = 'pk_prod_your_site_key_here';
		s.setAttribute('data-auto-init', '');
		document.head.appendChild(s);
	});
}

Load timing and weight

A third-party script on every page needs a performance budget, and the load point is a measurement, not a default. Measured on 2026-10-24 at SDK 3.8.0 as served (3.8.1 bytes differ slightly): the core is about 460 KB raw (about 200 KB gzip) and the extensions about 730 KB raw (about 305 KB gzip). After consent the extensions start two WebAssembly modules and dozens of short-lived blob workers (38 in one measured integration). Measure your own page; sizes change with every release.

If the core competes with first paint or hydration, inject it after load and the first idle period instead of in <head>. Safari has no requestIdleCallback, so fall back to setTimeout:

function loadNoxticaCore() {
	const s = document.createElement('script');
	s.src = 'https://collect.noxtica.com/collector/noxtica-collector-core.js';
	s.async = true;
	s.dataset.siteKey = 'pk_prod_your_site_key_here';
	s.setAttribute('data-auto-init', '');
	s.setAttribute('data-auto-check-once', '');
	document.head.appendChild(s);
}
const afterIdle = (fn) => ('requestIdleCallback' in window ? requestIdleCallback(fn, { timeout: 4000 }) : setTimeout(fn, 0));
if (document.readyState === 'complete') afterIdle(loadNoxticaCore);
else addEventListener('load', () => afterIdle(loadNoxticaCore), { once: true });

The core still parses as one main-thread task when it arrives. Keep the listener inline in <head> before this loader, keep the recorder head first if you use replay, and make sure the core is loaded before any protected action can fire.

Name what the collector writes in your cookie and privacy notices. Persistent keys are written by the extensions only, only under the deviceId category, and never by the core; in a gated mode nothing persistent is written before deviceId is granted, and withdrawing it deletes the three device keys. <sk> is your Site Key.

NameStoreLifetimeCategoryWritten by
nox_fp_<sk>first-party cookie, localStorage, IndexedDB nox_kv_<sk>400 daysdeviceIdExtensions
nox_itp_canary_<sk>first-party cookie, localStorage, IndexedDB nox_kv_<sk>400 daysdeviceIdExtensions
nox_tri_<sk>first-party cookie, localStorage, IndexedDB nox_kv_<sk>400 daysdeviceIdExtensions
nox_lock_<sk>localStorage (cross-tab lock)at most 30 seconds, removed after each checkdeviceIdExtensions, only while storage is open
noxtica.session.<sk>.tokensessionStorage (token cache)the browser sessionnone (security)Core and extensions
noxtica.session.<sk>.protect.<action>sessionStorage (protected-action hand-off)the browser sessionnone (security)Core
noxtica.session.khanvm, .gpuvmsessionStorage (engine load status)the browser sessionnoneExtensions
__noxtica_khan_accel_v1sessionStorage (capability cache)the browser sessionnoneExtensions
nx_replay_tab_id, nx_replay_session_v1sessionStorage (replay continuity)tab session, ends after 30 minutes idle by defaultreplayExtensions
nox_buf_<sk>IndexedDB (durable buffer for RUM and console-log events)undelivered events dropped after 72 hoursanalyticsExtensions
_nox_test, cookietestcapability probes, created and removed in the same callnonenoneExtensions

The three device cookies are set from script with Path=/, no Domain attribute (host-only), SameSite=Lax, and Secure on https, for 400 days. They are not HttpOnly: they are script-written by design, because the cookie is one redundant layer the SDK reads back to restore the other layers after a browser purges them. From SDK 3.8.1 the nox_fp_<sk> cookie holds only the state needed to recover the device identifier and its cache lifetime, as URI-encoded JSON: {"_stateSchemaVersion":2,"deviceId":"…","lastSubmittedAt":"<ISO time>"}. The last check result (score, risk level, confidence, flags) is written only to localStorage and IndexedDB (nox_kv_<sk>), never to the cookie; if only the cookie survives a browser purge, the identifier is restored and a cache hit reports no cached result until the next check. Before 3.8.1 the cookie also carried the last check result. Browsers may shorten script-written cookie lifetimes, so treat 400 days as the maximum.

Check that it works

  1. The core sets window.noxticaClient. Run typeof window.noxticaClient in the console.
  2. The Network tab shows POST /client/token to your collector host with status 200. Filter by the collector host.
  3. The noxtica:collected event fires (register your listener before the loader; afterwards read window.noxticaResult). In a gated mode before consent it carries skipped: 'consent_required'.
  4. The Backoffice shows the visit: the domain page lists recent events, and live monitoring shows the device within a minute or two.
  5. Server side, GET https://collect.noxtica.com/v1/device/DEVICE_ID with Authorization: Bearer sk_prod_your_api_key returns the record for the fingerprintId from the event. Handle 401, 403, 404, and 429 explicitly.
  6. Replay only: window.__noxticaHead is present, the replay policy is armed, and the replay category is granted.
SymptomLikely cause
window.__noxticaHead appears only after first paintCloudflare Rocket Loader deferred the recorder head. Add data-cfasync="false" and keep it first and synchronous.
Pinned tag blocked with an integrity errorA stale pin: the file left the retention window or you copied a hash from a document. Re-copy from Script & Consent.
noxtica:collected has skipped: 'consent_required'The domain is in a gated mode and setConsent was never called, or deviceId is not granted.
403 origin_mismatch on /client/tokenThe Site Key belongs to another origin. Use the key of the exact origin the page runs on.
Violations in the consoleA CSP directive from the table above is missing. See Content Security Policy.

Release checks, testing and versions

Guard the Site Key map as a production switch. If your code selects the Site Key by host, that map is the only thing that turns Noxtica on. A change that removes a key switches it off silently. Add a post-deploy smoke test that the served HTML contains the install tag for each production host, give the files that hold the map and the consent bridge a CODEOWNERS entry, and keep one key per origin set.

Test against a lab page, not production traffic. Headless browsers are bot-scored and can be answered by a CDN challenge, so risk scores from headless runs are noise: never compare them across configurations. To test an enforcing CSP or consent flow on the real origin, serve a minimal page that contains your tag from the browser’s own request interception, so the Site Key is accepted without sending traffic through your CDN. A spec outline:

  • with consent withheld, the only collector traffic is the core script and POST /client/token, and storage holds only the sessionStorage token;
  • after you deliver consent, the event sequence is noxtica:consent-changed, noxtica:extensions-loaded, then noxtica:collected with a fingerprintId;
  • a blocked extensions script yields noxtica:extensions-failed, and the next consent signal retries.

Versions. The loader version appears in the manifest version field, the header comment of the script, and window.noxtica.v (SDK 3.7.0 and later). Behavior has changed between releases: noxtica:ready and the pre-init queue arrived in 3.6.0, the session API in 3.7.0, and the object result of loadExtensions() with object-valued extension events in 3.8.0. Re-run your release check after each collector release. Where your account has the typed @noxtica/sdk package, write against its typings rather than against prose.

Browser responsibility

The browser loads the collector for the provisioned origin and public Site Key. It reports a collection result or an error state. If a result contains a device identifier, application code may pass that identifier to the customer backend for an authenticated lookup.

A server credential never belongs in browser JavaScript, HTML, a mobile application resource, a URL, or a client-visible log.

Evidence availability depends on the current browser, tenant configuration, privacy signals, and consent state. JavaScript disabled or blocked means no new browser assessment. Reduced evidence, a suppressed browser-visible result, and a transport error are different states; none is automatically low risk.

Optional behavioral or replay capture requires its separately reviewed eligibility and consent or lawful-basis path. The public loader alone does not authorize collection beyond the configuration approved for the evaluation.

Reliability checks

  • Register both completion and error handling before relying on a result.
  • Keep collection outside the page-rendering critical path.
  • Preserve the normal customer journey when collection cannot run.
  • Fail open with bounded waits. From consent to a collected result can take 8 to 11 seconds on a cold device; a budget of a second or two will usually expire on a first consented visit. Prefer the server-side verify response for the authoritative identifier over waiting in the browser.
  • Send only the returned device identifier to your backend.
  • Do not put identifiers, raw results, or credentials in URLs or optional analytics.
  • Test the browser versions and privacy modes your own application supports.

Next step

Follow Backend Integration for authenticated lookup states and Integration Flow for customer-owned policy rollout.