SDK Changelog
Release notes for the browser collector SDK, the core and extensions scripts served from your collector host. Each entry says what changed, who is affected and what to do. The newest release is first, and the version number is the one the collector reports: window.noxtica.v on a page that runs 3.7.0 or later.
Only one release needs a code change on your side: 3.8.0, tagged Breaking below. Every other release is additive or a fix, so a page that adopts none of the new surface keeps working. Releases before 3.5.2 are not listed. Install steps, consent modes, events and the command queue are described on Install and Browser Runtime.
The stable collector URLs serve the latest build. A hashed build you pinned with Subresource Integrity keeps serving for the last few releases, so to take a release, copy the new pinned tag from Backoffice → Integrations & API → Script & Consent → Snippets. The version number names a release, not the exact bytes: when a fix ships without a version bump, the hashed file name and its integrity value identify the build.
A client-side fingerprintId is a hint
The core (minimal) and extensions (full) runtimes legitimately mint different ids for one browser. A fingerprintId that arrives in noxtica:collected or noxtica:cache-hit is therefore a hint for your page, not an identity. The only authoritative id is the one returned to your backend by POST /v1/protected-actions/verify. Release 3.8.1 fixes a race so that one page load produces one page check and one submit; before it, the race could produce two noxtica:collected events with different ids. For the server-side step see Backend Integration.
Compatibility with @noxtica/sdk
@noxtica/sdk is the typed wrapper around the collector script. It does not contain the collector: the script always comes from your collector host, so the pair that runs on a page is one collector release (this page) and one wrapper release. The Developers page states what is currently published.
| Collector release | @noxtica/sdk | Notes |
|---|---|---|
| 3.8.1 | 0.3.0 (targets this release) | 0.3.0 needs a collector core of 3.8.0 or newer. |
| 3.8.0 | 0.3.0 (minimum supported core) | loadExtensions() resolves a result object (Breaking). |
| 3.7.x, 3.6.x, 3.5.2 | 0.2.x | 0.2.x types loadExtensions() as a boolean and passes the loader’s value through. On a 3.8.0 or newer core it still resolves the object at run time, which is always truthy: use .ok, or move to 0.3.0. |
Against a core older than 3.8.0, 0.3.0 turns the boolean into the 3.8.0 result shape: { ok: true, loaded: [] } for true and { ok: false, reason: 'unknown', code: 'legacy_loader' } for false. The session members (@noxtica/sdk/session, from 3.7.0) throw on a core older than 3.7.0, as track() does on a build that predates it. The wrapper has its own release notes for typings and framework helpers; this page covers the collector.
SDK 3.8.1 (2026-10-24)
Fixes and consent-handling changes; nothing is removed or renamed.
Fixed
- One page check is one submit. With consent delivered at
noxtica:ready,loadExtensions()called at the same point and a token response slower than the extensions script, a page check could run twice: two submits, two runtimes and twonoxtica:collectedevents with differentfingerprintIdvalues. A page-check call that is still waiting for the token now joins the runtime when it attached in the meantime. One page load produces one submit.- Who: pages that grant consent and call
loadExtensions()early, withdata-auto-check-onceorcheckOnce(); most visible on cold devices where the token round trip is slower than loading the extensions script. - Do: nothing. Remove any de-duplication you added around
noxtica:collected, and take the authoritative id from your server-side verify call.
- Who: pages that grant consent and call
- No second skipped event. Replaying an already-settled, skipped page check no longer dispatches a second
noxtica:collectedwithskipped: 'consent_required'. Only a real result is announced. - One token per page session across the core to extensions hand-off. On the documented install (the core with auto-loaded extensions) the extensions runtime could mint its own token a few seconds after the core’s. A payload the core had collected, sealed under the first token, was then submitted under the second token and the server answered
400 Unable to process request. The core now hands its token to the runtime, which adopts it when it has more than 30 seconds of life left; later rotations mint normally.- Who: pages on the documented install that call
collect()thensubmit()(orcheckOnce()) with the extensions loaded, on a domain with KHAN armed or a guarded RUM policy. - Do: nothing. No configuration or server change. Re-copy a pinned tag to get the build.
- Who: pages on the documented install that call
Changed
- The pre-init command queue is coalesced. When the core replays
window.noxtica.q, everysetConsentorconsentupdate folds into one update (a later key wins) applied at the position of the first one, so you get onenoxtica:consent-changed. A queuedloadExtensionsis dropped when the folded state denies all four categories: it answersnoxtica:extensions-failedwith{ ok: false, reason: 'consent_missing', code: 'consent_withdrawn' }. Invalid entries andconsent gcmrun unchanged. A later livenoxtica('loadExtensions')is an explicit call and still loads.- Who: pages that queue several consent updates before the core loads, or queue
loadExtensionsahead of a consent banner. - Do: expect one
noxtica:consent-changedfor the whole queue. Prefer deciding consent in anoxtica:readyhandler over queuing it.
- Who: pages that queue several consent updates before the core loads, or queue
- The signed consent mode is announced, and
getConsent()says whether it is known.noxtica:consent-changednow also fires when the mode signed for your domain differs from the page’s declaration at token time (for example an untagged page on a domain set to API mode:not_requiredbecomesapi).getConsent()and the eventdetailcarrymodeResolved, which isfalseuntil the token applied the signed mode. Auto-loading extensions (data-extensions="auto") fails closed: before the mode is known it needs an explicit grant, and it never runs when every category is denied. A manualloadExtensions()is unchanged.- Who: pages that rely on auto-load without declaring a consent mode on the tag, and code that compares
getConsent().modewith the tag’s declaration. - Do: declare the mode on the tag (
data-consent-mode) or grant consent explicitly, and gate your own logic onmodeResolvedrather than onmodealone.
- Who: pages that rely on auto-load without declaring a consent mode on the tag, and code that compares
- The device cookie is minimal.
nox_fp_<siteKey>now carries only the state schema version, the device id and the time of the last submit, which is what recovery of the device id and its cache lifetime need. The last result (risk score, level, confidence, flags) stays inlocalStorageand IndexedDB. A cache hit recovered from the cookie alone returnscachedResult: null.- Who: sites that describe cookie contents in a cookie notice, and code that reads
cachedResultafter the cookie was the only surviving storage. - Do: update the notice wording, and treat
cachedResult: nullas a prompt to check again.
- Who: sites that describe cookie contents in a cookie notice, and code that reads
SDK 3.8.0 (2026-10-12), Breaking
loadExtensions() resolves a result object instead of a boolean. An object is always truthy, so if (await client.loadExtensions()) and === true no longer test success.
Changed
loadExtensions()never rejects and resolves a result. This coversclient.loadExtensions(options), the staticNoxticaCollector.loadExtensions()andnoxtica('loadExtensions', options). Success is{ ok: true, loaded: string[] }, whereloadedis the extensions script URL the core injected ([]on the combined bundle, or when a tag you added yourself attached the runtime); later calls resolve the same shape without a second fetch. Failure is{ ok: false, reason, code, url?, status?, error? }:reasonis a closed set,codeis the finer machine-readable cause,urlis the attempted file,statusthe HTTP status when one was seen anderrorhuman text that you should not parse.- The extensions events carry the result.
noxtica:extensions-loadedandnoxtica:extensions-failednow havedetailof{ message: 'attached' | 'not-loaded', ...result }. In 3.6.0 and 3.7.0 the detail was that string; read it fromdetail.message. Only a real load attempt dispatchesnoxtica:extensions-failed. loadExtensions({ requireConsent: true })refuses to fetch in a gated mode (API or cookie) while no category is granted and answers{ ok: false, reason: 'consent_missing', code: 'consent_required' }. The plain call needs no consent, as before.
reason | Meaning |
|---|---|
consent_missing | Only with requireConsent: true, or when a queued call was dropped because every category is denied (3.8.1). Nothing is fetched. |
csp_blocked | An enforced Content-Security-Policy violation blocked the script URL. code is the effective directive (script-src-elem or script-src). |
integrity_mismatch | The browser rejected the pinned file and its hash differs from the pin (code: sha384_mismatch). |
network | The file could not be fetched (code: http_<status> or fetch_failed). |
timeout | No load or error event within 20 seconds (code: script_timeout). The next call retries. |
already_loaded | A script tag for the URL is already in the page but nothing registered (code: script_present). Nothing is injected. |
unknown | Everything else, for example a build mismatch or a hand-off error. |
The reason is best effort: the browser’s error event carries no cause, so a failed script costs one extra diagnostic request for the same file. A Content-Security-Policy whose connect-src also blocks that request reads as network.
- Who: code that tests the return value, or that reads the
detailof the extensions events as a string. - Do: test
result.ok, logresult.reasonandresult.codewhen it isfalse, and read the old string fromdetail.message. With@noxtica/sdk, move to 0.3.0 for the matching types.
SDK 3.7.0 (2026-10-09)
Additive. A page that uses none of it behaves as 3.6.0, except that the first core or combined bundle publishes window.noxtica.v (creating the command-queue stub when the page defined none) and getConsent() and noxtica:consent-changed details carry privacySignal.
Added
-
Session API.
setTag(replaces a key’s values), hashedidentify(the raw id is never stored or sent),event,getMetadata,upgrade, and a byte-capped pre-roll for conditional recording. Queue commands:set,identify,event,metadata,upgrade. -
Session lifecycle.
pause(),resume(),stop()andstart()on the client and as queue commands. All are idempotent. -
Google Consent Mode v2 bridge.
data-consent-mode="gcm",createClient({ consentMode: 'gcm' }),client.enableGoogleConsentMode()ornoxtica('consent', 'gcm')make the collector followanalytics_storage. -
GPC and DNT on the tracker and console lanes. With the default signed policy (
honor), a visitor who sends Global Privacy Control or Do Not Track gets neither the tracker nor console-log capture.getConsent().privacySignalreports'gpc','dnt'ornull, as a local flag that is never sent. Thedocumented_overridepolicy runs both lanes. -
Duplicate-tag guard (
NX001). The first core or combined bundle on a page claims it and setswindow.noxtica.v. A second one logsNX001and is refused, so no second client is created. -
Who: everyone gets
window.noxtica.vandprivacySignal; the rest is opt-in. Pages with two collector tags (a hand-pasted tag plus a tag-manager copy) now see theNX001warning and only the first one runs. -
Do: remove the extra tag if you see
NX001. If your tracker lane must also run for visitors who send GPC or DNT, review the domain policy before you upgrade.
SDK 3.6.0 (2026-10-08)
Additive. A page that uses none of it behaves as 3.5.2; the visible differences are that window.noxtica is installed as a function by auto-init and noxtica:ready is dispatched.
Added
-
noxtica:readyondocument(detail: { client }), dispatched once per auto-init tag after the queue ran and before the automatic page check starts. A page withoutdata-auto-initgets neither the event nor the queue. -
The
window.noxticacommand queue (the gtag pattern): definewindow.noxtica = window.noxtica || function () { (window.noxtica.q = window.noxtica.q || []).push(arguments); }before the core loads. The core replays the queue in order against the first client it creates, then makeswindow.noxtica(...)live. Commands:setConsent,loadExtensions,protect,checkOnce. Unknown commands log a warning, and failures go tonoxtica:errorinstead of throwing. -
noxtica:consent-changed(detailis thegetConsent()snapshot), dispatched only when a consent category really changes. -
noxtica:extensions-loadedandnoxtica:extensions-failed, dispatched by the core (never by the combined or extensions bundles). The detail was the string'attached'or'not-loaded'; since 3.8.0 it is the result object. -
Same-page re-collect is evented. In API or cookie mode, a page check skipped for consent (
noxtica:collectedwithskipped: 'consent_required') runs again with its original options whendeviceIdis granted later on the same page, and announcesnoxtica:collected(andnoxtica:cache-hitwhen the cache answers it). -
Who: pages that want to react to readiness, consent or extension loading without polling, and consent banners that load after the collector.
-
Do: adopt
noxtica:readyand the queue when convenient.
SDK 3.5.2 (2026-10-05)
Additive; completes 3.5.1 of the same day.
Changed
-
The legacy URLs serve the core.
/collector/noxtica-collector.js(3.5.1) and/collector/noxtica.js(3.5.2) return the same bytes, content type, CORS headers and cache class as/collector/noxtica-collector-core.js. A core evaluated from a legacy URL defaultsextensionsto'auto': the extensions load immediately in consent modenot_required, and on the first category grant in API or cookie mode. An explicitextensionsoption ordata-extensionsvalue still wins (any value other thanautoopts out), and the core URL itself keeps the manual default. -
Pinned combined builds keep working. The combined bundle is still built, and its hashed file keeps serving the combined bytes for existing integrity pins.
asset-manifest.jsonaddsservedAsanddeprecatedentries for the legacy and combined files. -
Who: pages that still load a legacy URL. Before the extensions attach, extension-lane client methods (
track, tracker and replay setters) run the core’s reduced lane and are not replayed after the hand-off;maskTextFnandmaskInputFnapply only once the extensions runtime owns the client. -
Do: nothing is required. Move to the core tag plus extensions when convenient, as described on Install and Browser Runtime.