Network & privacy
What actually goes over the wire, how often, and what is kept on the device. If you are reviewing the SDK before putting it on a production site, this is the page to read.
Request budget
The SDK is deliberately quiet. In a normal session it makes very few requests.
| Request | When | Frequency |
|---|---|---|
GET /sdk/experiences | Session start, for the registered form set | Once per session. Normally a 304 with no body. |
GET /sdk/forms/:id | A showForm() for a form not in the catalog | On demand, once per form. |
POST /sdk/events | Batched events, impressions, dismissals | Every flushIntervalMs (default 5s) if there is anything queued. |
POST /sdk/forms/:id/response | A user submits a response | Once per response. |
There is no POST /sdk/init. SDK name and version ride on an X-Intunix-Sdk header on the catalog request instead.
Catalog caching
The form catalog is fetched once per session, per registered form set. A page load, a reload, or an SPA navigation inside a live session with an unchanged register makes zero catalog requests — the localStorage cache is authoritative.
The cache entry carries two fingerprints, and both must match to be a hit:
- The session it was fetched for.
- The exact set of forms that was requested.
So changing register invalidates the cache immediately, even mid-session, and triggers a refetch. The requested set is sorted before being sent, which means ['b', 'a'] and ['a', 'b'] produce an identical request and share a cache entry.
The first fetch for a given session and set sends If-None-Match. The server's ETag incorporates a hash of the requested set, so two apps requesting different lists never share a 304 with each other.
Offline behaviour
- Queued events are persisted, and replayed on the next successful init.
- Failed uploads retry with backoff rather than being dropped.
- A pending batch is flushed with
navigator.sendBeacononpagehide, so closing a tab does not lose it. - With a cached catalog, forms continue to show and record outcomes while offline. Responses upload when connectivity returns.
What is sent
On a response
{
"form_id": "cx_01H8...",
"answers": { /* the user's answers */ },
"context": { "courseId": "c-101" }, // whatever you attached
"anonymous_id": "anon_9f3...",
"user_id": "user_42", // only if identify() was called
"session_id": "sess_7c1..."
}On an event batch
Each entry carries the event name, your properties, a timestamp, the session id, and the current identity. Impressions and dismissals ride in the same batch as $form_shown and $form_dismissed when the catalog enables that.
What the SDK never collects
- No cookies are set.
- No fingerprinting — no canvas, no font enumeration, no device probing.
- No cross-site tracking. The anonymous id is per-origin and per-device.
- No automatic capture of form fields, keystrokes, clicks, or page content.
- No third-party requests. Traffic goes to your Intunix API host and the CDN, nothing else.
Everything sent is something you passed in: traits you set, events you tracked, context you attached, answers the user typed.
Device storage
Six keys, listed in Identity & sessions. All are first-party localStorage or sessionStorage on your own origin. If both are blocked, the SDK degrades to in-memory state — forms still work, but cadence resets on every page load.
Content Security Policy
If you run a strict CSP, allow:
script-src https://cdn.intunix.com;
connect-src https://api.intunix.com;The npm packages need only the connect-src entry — there is no CDN script involved. Forms are rendered by your own framework (React/Vue) or inside a Shadow DOM (script tag), so no style-src exception is required.
Bundle size
The core engine is small and tree-shakeable, and ships as both ESM and CJS with full type declarations. No polyfills, no runtime CSS file, no web fonts. The React and Vue packages declare their framework as a peer dependency, so nothing is duplicated into your bundle.
Data residency and retention
Responses and events are stored in your Intunix workspace, subject to your plan's retention settings. The SDK itself keeps nothing beyond the device keys above, and reset() clears the identity-linked ones on demand.