Web SDK
The Kixo Web SDK auto-tracks clicks, page views, sessions, errors, scroll depth, network failures, web-vitals, rage-clicks, dead-clicks, and heatmap data with a two-line embed. Distributed as a native ES module — works in every browser shipped after ~2018.
Installation
Script tag (CDN)
Add the snippet before the closing </head> tag. Note the type="module" — required because the SDK is an ES module with a lazy-loaded replay chunk.
<script type="module" src="https://cdn.kixo.io/kixo.min.js"></script>
<script type="module">
import Kixo from 'https://cdn.kixo.io/kixo.min.js';
Kixo.init({ projectId: 'YOUR_PROJECT_ID', apiKey: 'YOUR_API_KEY' });
</script>Note
The SDK also exposes itself as window.Kixo on import, so legacy non-module inline scripts can poll for it: function init(){if(window.Kixo){Kixo.init({...})}else setTimeout(init,50)}init().
npm
npm install @kixo/webimport Kixo from '@kixo/web';
Kixo.init({
projectId: 'YOUR_PROJECT_ID',
apiKey: 'YOUR_API_KEY',
});No-Code Platforms
If you are building with an AI-powered builder like Lovable, Bolt, v0, or Replit, paste the script-tag snippet directly into your builder's chat or code-injection settings. Most builders support adding scripts to the <head> of your site.
Configuration
The two-line embed is enough — every auto-tracker is on by default. For finer control, pass any of these to Kixo.init():
Kixo.init({
projectId: 'YOUR_PROJECT_ID', // required
apiKey: 'YOUR_API_KEY', // required
// Per-tracker toggles — every flag here defaults to true.
autoTrack: {
pageViews: true,
clicks: true,
scrollDepth: true,
sessions: true,
forms: true,
network: true,
errors: true,
performance: true,
rageClicks: true,
deadClicks: true,
},
// Heatmap recording (clicks + scroll on by default; mouse-move opt-in).
heatmap: {
enabled: true,
clicks: true,
moves: false,
scroll: true,
},
// Session replay — opt-in. 10% sample rate by default.
replay: {
enabled: false,
sampleRate: 0.1,
maskInputs: true,
},
debug: false, // verbose console logs
});Note
Server-controlled config.Every per-tracker flag above can also be flipped from your dashboard'sSettings → Data Collection page. Server values override the local Kixo.init defaults; project preferences override admin globals; the local init args lose every collision.
Auto-tracked events
With the default configuration, Kixo automatically captures these events with no additional code:
page_view— every navigation (initial load + SPA route changes)session_start/session_endclick— all click interactions with element selectorscroll_depth— 25 / 50 / 75 / 100 % thresholdsrage_click— repeated clicks on the same elementdead_click— clicks on non-interactive elementserror— uncaught JavaScript exceptions + promise rejectionsperformance— page-load Web Vitals (LCP, FID, CLS, INP)heatmap_click/scroll— heatmap data
See the full list in the Events Reference.
Custom events
Kixo.track()
Send a custom event with optional properties.
Kixo.track('purchase_completed', {
product_id: 'SKU-123',
amount: 49.99,
currency: 'USD',
});Typed event helpers
Sugar over Kixo.track() for the events Kixo recognises by name (purchase, signup, subscribe_start,trial_start, cancel, upgrade,activation, share, invite). Typed wrappers buy compile-time property validation and a single source of truth for key names — backend's standard-event detector matches verbatim.
Kixo.trackPurchase({ amount: 49.99, currency: 'USD', productId: 'pro_yearly' });
Kixo.trackSubscriptionStart({
plan: 'pro',
amount: 9.99,
currency: 'USD',
interval: 'month',
});
Kixo.trackSignup({ method: 'google' });
Kixo.trackTrialStart({ plan: 'pro', days: 14 });
Kixo.trackCancel({ plan: 'pro', reason: 'too_expensive' });
Kixo.trackUpgrade({ fromPlan: 'free', toPlan: 'pro' });
Kixo.trackActivation({ event: 'first_post_published' });
Kixo.trackShare({ channel: 'twitter', contentId: 'post_123' });
Kixo.trackInvite({ channel: 'email', recipientCount: 5 });Kixo.identify()
Associate the current device with a known user. Reserved standard property keys carry a $-prefix (Mixpanel convention) so they namespace away from your own custom traits and promote to the dashboard's profile columns — see the Standard property catalog below for the full 37-key list.
Kixo.identify('user_123', {
$email: 'jane@example.com', // identity
$name: 'Jane Doe', // identity
$plan: 'pro', // subscription pack
$lifetime_orders: 12, // e-commerce pack
signup_source: 'twitter_ad', // custom trait
});Kixo.setUserProperty() — tag a user for segmentation
Attach arbitrary key/value attributes to the current user. Values can be strings, numbers, or booleans — the boolean form is the cleanest way to tag a user for later targeting in segments, email campaigns, or chat queries.
// Tag a user as subscribed — instant segment "Subscribed users"
Kixo.setUserProperty('subscribe', true);
// Mark a VIP — used in campaign targeting + chat ("show me VIPs")
Kixo.setUserProperty('vip', true);
// Numeric and string values work too
Kixo.setUserProperty('plan_tier', 'enterprise');
Kixo.setUserProperty('lifetime_orders', 42);
// Bulk-set
Kixo.setUserProperties({ subscribe: true, plan_tier: 'enterprise' });Properties persist in localStorage across reloads and auto-attach to subsequent events. Use them in chat with prompts like "build an email campaign for users where subscribe is true" — Kixo synthesises a segment + drafts the template automatically. Cleared on Kixo.reset().
Kixo.group()
Associate the user with a company or organisation.
Kixo.group('company_456', {
name: 'Acme Inc',
plan: 'enterprise',
});Kixo.reset()
Clear identity, super-properties, and the persisted queue. Call this on logout so subsequent events are not attributed to the previous user.
Kixo.reset();Standard property catalog
Reserved property keys carry a $prefix so they namespace away from your custom traits. Kixo's catalog covers 37 keys across 3 universal packs (identity, geo, lifecycle) and 5 B2B vertical packs (subscription, e-commerce, media, marketplace, loyalty). Set whichever apply to your product — the dashboard adapts and renders only the packs you populate.
Identity
Always relevant. Sets the profile header columns.
| Key | Type | Description |
|---|---|---|
$email | string | Primary email, often the merge key for identity stitching. |
$phone | string | E.164 phone number. |
$name | string | Full display name. |
$first_name | string | Given name. |
$last_name | string | Family name. |
$avatar_url | string | Full URL to the user's avatar image. |
Geo
Geographic context.
| Key | Type | Description |
|---|---|---|
$country | string | ISO 3166 country code. |
$city | string | City name. |
$region | string | State or province. |
$timezone | string | IANA zone like America/Los_Angeles. |
$language | string | IETF tag like en or ru-RU. |
$locale | string | Full locale identifier. |
Lifecycle
When did we see them.
| Key | Type | Description |
|---|---|---|
$created | ISO8601 | Signup or account creation time. |
$last_seen | ISO8601 | Last engagement time. |
Subscription
Set if your product has plans.
| Key | Type | Description |
|---|---|---|
$plan | string | Tier slug — free, pro, enterprise. |
$subscription_status | string | active / trial / cancelled / past_due. |
$trial_ends | ISO8601 | When the current trial expires. |
$mrr | number | Monthly recurring revenue in account currency. |
$subscription_started | ISO8601 | When the current subscription began. |
E-commerce
Set if you sell products.
| Key | Type | Description |
|---|---|---|
$lifetime_orders | number | Count of completed orders. |
$lifetime_revenue | number | Total spend. |
$aov | number | Average order value. |
$last_purchase | ISO8601 | Most recent successful purchase. |
$first_purchase | ISO8601 | First successful purchase. |
$cart_abandoned_count | number | Lifetime count of cart abandonments. |
Media
Set if you publish content.
| Key | Type | Description |
|---|---|---|
$content_tier | string | free / premium / paid. |
$subscribed_categories | CSV string or array | Categories the user follows. |
$watch_time_total | number | Lifetime watch time in seconds. |
$last_played | ISO8601 | Most recent playback start. |
Marketplace
Set if you're a two-sided platform.
| Key | Type | Description |
|---|---|---|
$seller_tier | string | Seller-side tier slug. |
$buyer_tier | string | Buyer-side tier slug. |
$listings_count | number | Active listings the user owns. |
$reviews_count | number | Reviews the user has received. |
$verified | boolean | KYC status. |
Loyalty
Set for engagement and rewards programs.
| Key | Type | Description |
|---|---|---|
$loyalty_points | number | Current redeemable points balance. |
$vip_level | string | VIP tier slug. |
$referral_count | number | Successful referrals attributed to this user. |
Tip
Don't see your pattern? Use bare keys for custom traits. They surface in the dashboard's Custom Traits panel without polluting the profile columns. The 5 vertical packs above are opinionated guesses at the most common B2B shapes — customer-specific terminology (e.g. shipping_plan) stays bare.
Super-properties
Per-session key/value pairs auto-attached to every outbound event. Different from identify() traits (which describe the identity); super-properties describe session context — active A/B variant, build flavour, opted-in feature flags, affiliate ref. Persisted in localStorage across reloads; cleared on reset(). Per-event properties on track() always win on key collision.
Kixo.setSuperProperty('build_flavour', 'beta');
Kixo.setSuperProperties({ ab_variant: 'B', referrer_campaign: 'autumn-launch' });
// Sugar for A/B tracking — keys as 'experiment_<id>' so backend
// can run direct WHERE filters on experiment analysis.
Kixo.setExperimentVariant('checkout_v2', 'variant_a');
Kixo.unsetSuperProperty('build_flavour');
Kixo.clearSuperProperties();Heatmaps
Heatmap recording is on by default — clicks and scroll depth, both sampled at 100 %. Mouse movement is opt-in (high-volume; enable per- page if useful).
Kixo.init({
projectId: 'YOUR_PROJECT_ID',
apiKey: 'YOUR_API_KEY',
heatmap: { mouseMovement: true }, // turn on full-resolution mouse-move
});Session replay
Replay is opt-in. Defaults to a 10 % sample rate — sessions that fall outside the sample never load the recorder. The recorder is code-split into a separate recorder.min.jschunk that lazy-loads only when replay is on, so the cold bundle stays under 20 KB gzipped for the 95 % of sessions that don't use replay.
Kixo.init({
projectId: 'YOUR_PROJECT_ID',
apiKey: 'YOUR_API_KEY',
replay: { enabled: true, sampleRate: 0.25 }, // record 25 % of sessions
});Feature flags
Check flag values at runtime via Kixo.getFeatureFlag().
const variant = Kixo.getFeatureFlag('new_checkout');
if (variant === 'enabled') {
showNewCheckout();
} else {
showLegacyCheckout();
}Resilience model
The SDK ships with a four-stage state machine that makes it safe to embed on any production site:
- initialising — set the moment
Kixo.initruns. Events that auto-trackers fire before the first config response are dropped on the floor (the server may saypaused: truefor the project or this SDK version, and we shouldn't buffer events for a session we're about to silence). - running — first config response succeeds. Events flow normally. Failed batches retry on a 5 / 10 / 20 / 30 / 60 s schedule; 10 consecutive failures move to a 60 s heartbeat; 30 failures move to a 30-minute cooldown.
- recovering — instead of hammering the batch endpoint forever during cooldown, the SDK pauses and re-fetches
/api/sdk/init. If the server returnspaused: truemid-outage (you flipped the kill-switch while clients were dark), the SDK transitions topausedByServerand stops trying. - pausedByServer — events drop at
enqueuetime. The SDK polls config periodically and resumes the moment you flip the kill-switch back off.
The local queue caps at 200 events with FIFO drop-oldest, so a long offline window can never exhaust browser memory. Retry delays carry ±25 % jitter to spread reconnects across the fleet.
Diagnostics
Read-only health snapshot — useful for "why aren't my events flowing?" debugging in dev tools.
console.log(Kixo.diagnostics());
// {
// initialised: true,
// paused: false,
// queue: {
// bufferedEventCount: 7,
// bufferCap: 200,
// consecutiveFailures: 0,
// isFlushing: false,
// retryTier: 'healthy',
// paused: false,
// persistDisabled: false,
// lastSuccessAt: 1730127200000,
// lastAttemptAt: 1730127200000,
// },
// }