Skip to content

Browser SDK

The core entry of @leadmaps/tracking exposes everything you need for pageview, custom events, identification, consent, and identity reads. The auto-capture features (Web Vitals, errors, clicks, scroll depth, engaged time, and Experience) live behind lazy modules or their own subpaths.

leadmaps is the analytics service that receives these events. Your visitors’ data goes to a store you control, in EU-resident infrastructure by default.

import { init } from '@leadmaps/tracking';
init({
apiKey: 'YOUR_API_KEY',
siteId: '11111111-1111-4111-8111-111111111111',
host: 'https://collect.leadmaps.nl',
// optional:
mode: 'cookied',
autoCapture: true,
sessionTimeout: 30 * 60 * 1000,
respectDnt: true,
defaultConsent: 'unknown',
experience: true,
sampling: { mousemove: 0.1 },
});
FieldTypeDefaultNotes
apiKeystringrequiredIngest key from your leadmaps workspace.
siteIdstringrequired on hosted leadmapsSite UUID sent as X-Site-Id.
hoststringrequiredCollector origin (scheme + host). Throws on empty.
sessionTimeoutnumber30 * 60 * 1000ms. Negative / non-finite values clamp silently.
respectDntbooleantrueWhen true, a DNT-blocked init never installs listeners.
mode'cookied' | 'cookieless''cookieless'Cookieless sets no cookie or localStorage identity. Cookied identity is explicit opt-in.
privacy'anonymous' | 'consent''consent'Anonymous mode performs no browser storage or fingerprinting and skips replay, auto-capture, and feature flags.
defaultConsent'unknown' | 'granted''unknown'Pass 'granted' if you obtained consent before bootstrap.
samplingRecord<string, number>{}Per-event-type rates in [0, 1].
experienceboolean | ExperienceOptionsremote site settingConsent-gated, lazy interaction summaries. false always disables it. See Experience.

Calling init more than once is a no-op for the second call onward.

Omit mode to use cookieless identity. The SDK derives a daily-rotating anonymous id from coarse browser characteristics. It sets no cookie and writes no identity to localStorage. A session id may use tab-scoped sessionStorage to keep navigation within one tab connected.

Set mode: 'cookied' only when you deliberately need a persistent first-party identifier across days. That mode writes the _strk_aid cookie with a localStorage backup and stores session and identified-user continuity in localStorage, but only after consent. Use it when the same consenting browser should remain one Unique visitor across return visits.

Cookieless and anonymous identifiers rotate daily. In a range spanning more than one UTC day, the same browser can therefore contribute more than one Unique visitor. Unique visitors is always an estimate of distinct browsers, not a verified count of people.

Cookieless identity is not the same as anonymous privacy mode. A site using privacy: 'anonymous' performs no browser storage or fingerprinting, and the collector derives a daily aggregate key server-side. See Consent gating before choosing a mode. Cookieless identity does not by itself remove consent requirements.

import { track } from '@leadmaps/tracking';
track('checkout_completed', {
order_id: 'ord_42',
total_cents: 4900,
currency: 'USD',
});

name becomes the wire type discriminator. props is sanitized (string clamp, function / symbol drop) before queuing. Empty name is dropped silently. Calls before init are dropped.

For type-safe track() calls narrowed to your tracking plan, see TypeScript codegen.

import { identify } from '@leadmaps/tracking';
identify('user_42', { plan: 'pro' });

Binds the current anon_id to a known user_id. If a different user_id was previously stored, leadmaps emits a merge event before the new identify so cross-device timelines stay consistent. Empty / non-string userId produces a console.warn and is otherwise a no-op (the SDK never throws from public surfaces inside the host page).

init() records the first pageview automatically and listens for SPA navigation (pushState / replaceState / popstate). You do not need to call pageview() manually in 99 % of apps. If your router uses a non-history mechanism, call captureContext(url) followed by your custom emit.

import { grantConsent, revokeConsent, getConsentState } from '@leadmaps/tracking';
grantConsent(); // optional: pass a token attached as X-Consent on every request
revokeConsent(); // purges the in-memory queue immediately
getConsentState(); // 'unknown' | 'granted' | 'revoked'

While consent is 'unknown' (the default), events queue in memory but no network request goes out. grantConsent() drains the queued events. revokeConsent() empties the queue and makes every subsequent send a no-op for the rest of the page lifetime.

See Consent gating for the server-side enforcement.

import { getUserId, getAnonId, getAnonIdAsync, getSessionId } from '@leadmaps/tracking';
getUserId(); // string | null — null if identify() never ran
getAnonId(); // string — UUID in cookied mode, daily fingerprint in cookieless mode
await getAnonIdAsync(); // waits for the stable cookied id before returning
getSessionId(); // string — mints a new session id if the inactivity window has lapsed

getSessionId() extends lastActiveTs as a side effect. In the cookieless default, the session id may use tab-scoped sessionStorage. Cookied mode uses localStorage for cross-tab persistence.

Use getAnonIdAsync() when starting an optional module that must bind to the same persistent id as queued events. Regular tracking calls handle this wait automatically.

Consented installs with auto-capture send a small page-presence signal for the active visitor count. A visible tab counts as active, closing or hiding the last tab clears it, and an automatic expiry covers browsers that suppress a close signal. Multiple tabs from one browser still count once. Presence is not stored as an analytics event and does not consume the event quota.

Anonymous mode does not send presence. Older SDKs and installs using a custom event proxy keep the recent-event fallback until they are upgraded or their proxy adds the presence route.

import { captureContext, getCurrentContext } from '@leadmaps/tracking';
captureContext('https://example.com/pricing');
// returns: { utm, referrer, viewport, screen, language, timezone, ua }
getCurrentContext(); // EventContext | null

captureContext is called automatically on every pageview; you only need to call it manually if you are wiring your own custom navigation hook.

import type {
InitOptions,
TrackingEvent,
ConsentState,
} from '@leadmaps/tracking';

TrackingEvent is exported only for type narrowing in adjacent modules (e.g. the errors and engaged-time subpath signatures). Customers do not construct TrackingEvent instances directly.

The SDK exports send, getConfig, and getQueueForTests from the core entry for use by the opt-in subpaths. They are not part of the contract, so do not import them in production code. Test seams (__resetForTests, __resetConsentForTests, __resetIdentifyForTests, etc.) are private.