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.

html
<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

bash
npm install @kixo/web
js
import 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():

js
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_end
  • click — all click interactions with element selector
  • scroll_depth — 25 / 50 / 75 / 100 % thresholds
  • rage_click — repeated clicks on the same element
  • dead_click — clicks on non-interactive elements
  • error — uncaught JavaScript exceptions + promise rejections
  • performance — 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.

js
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.

js
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.

js
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.

js
// 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.

js
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.

js
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.

KeyTypeDescription
$emailstringPrimary email, often the merge key for identity stitching.
$phonestringE.164 phone number.
$namestringFull display name.
$first_namestringGiven name.
$last_namestringFamily name.
$avatar_urlstringFull URL to the user's avatar image.

Geo

Geographic context.

KeyTypeDescription
$countrystringISO 3166 country code.
$citystringCity name.
$regionstringState or province.
$timezonestringIANA zone like America/Los_Angeles.
$languagestringIETF tag like en or ru-RU.
$localestringFull locale identifier.

Lifecycle

When did we see them.

KeyTypeDescription
$createdISO8601Signup or account creation time.
$last_seenISO8601Last engagement time.

Subscription

Set if your product has plans.

KeyTypeDescription
$planstringTier slug — free, pro, enterprise.
$subscription_statusstringactive / trial / cancelled / past_due.
$trial_endsISO8601When the current trial expires.
$mrrnumberMonthly recurring revenue in account currency.
$subscription_startedISO8601When the current subscription began.

E-commerce

Set if you sell products.

KeyTypeDescription
$lifetime_ordersnumberCount of completed orders.
$lifetime_revenuenumberTotal spend.
$aovnumberAverage order value.
$last_purchaseISO8601Most recent successful purchase.
$first_purchaseISO8601First successful purchase.
$cart_abandoned_countnumberLifetime count of cart abandonments.

Media

Set if you publish content.

KeyTypeDescription
$content_tierstringfree / premium / paid.
$subscribed_categoriesCSV string or arrayCategories the user follows.
$watch_time_totalnumberLifetime watch time in seconds.
$last_playedISO8601Most recent playback start.

Marketplace

Set if you're a two-sided platform.

KeyTypeDescription
$seller_tierstringSeller-side tier slug.
$buyer_tierstringBuyer-side tier slug.
$listings_countnumberActive listings the user owns.
$reviews_countnumberReviews the user has received.
$verifiedbooleanKYC status.

Loyalty

Set for engagement and rewards programs.

KeyTypeDescription
$loyalty_pointsnumberCurrent redeemable points balance.
$vip_levelstringVIP tier slug.
$referral_countnumberSuccessful 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.

js
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).

js
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.

js
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().

js
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.init runs. Events that auto-trackers fire before the first config response are dropped on the floor (the server may say paused: 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 returns paused: true mid-outage (you flipped the kill-switch while clients were dark), the SDK transitions to pausedByServer and stops trying.
  • pausedByServer — events drop at enqueue time. 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.

js
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,
//   },
// }