SplitWisp

Docs

Pricing

Dashboard

SDK Reference

Ultra-lightweight A/B testing SDK. Zero dependencies. ~3KB gzipped.

Works with any website or JavaScript framework — vanilla JS, React, Vue, Angular, Next.js, or any SPA. Integrates with both the dashboard and the Visual Editor Chrome extension.

Installation

Script Tag (CDN / GTM)

The fastest way to get started. Add to every page you want to test:

<!-- Anti-flicker cloak (optional but recommended for visual tests) -->
<style>.sw-cloak { opacity: 0 !important; }</style>
<body class="sw-cloak">

<!-- SplitWisp SDK -->
<script src="https://cdn.splitwisp.com/sdk/v1/tracker.umd.js"></script>
<script>
  SplitWisp.init({
    apiKey: 'pk_live_abc123',
    endpoint: 'https://api.splitwisp.com',
  }).then(function (t) {
    // Visual changes are applied automatically.
    // For code-based experiments, read the variant:
    var variant = t.getVariant('exp_hero_headline');
    if (variant && variant.variantName === 'short') {
      document.querySelector('h1').textContent = 'Ship faster.';
    }
  });
</script>

The SplitWisp.init() convenience factory creates a Tracker instance, calls init(), and returns the tracker via a Promise. This is the recommended approach for <script> tag and Google Tag Manager usage.

npm / Bundler

npm install @splitwisp/tracker
import { Tracker } from '@splitwisp/tracker';

const tracker = new Tracker({
  apiKey: 'pk_live_abc123',
  endpoint: 'https://api.splitwisp.com',
});

await tracker.init();

// Check variant assignment
const variant = tracker.getVariant('exp_pricing_cta');
if (variant?.variantName === 'annual_first') {
  showAnnualPricingFirst();
}

// Track a conversion
tracker.trackConversion('exp_pricing_cta', 9900);

Configuration

Pass these options to the Tracker constructor or SplitWisp.init():

OptionTypeRequiredDefaultDescription
apiKeystringYesYour project API key (starts with pk_live_)
endpointstringYesAPI base URL, e.g. https://api.splitwisp.com (no trailing slash)
sessionIdstringNoauto-generatedOverride the auto-generated session ID. Useful for server-side rendering or cross-domain tracking
userIdstringNoKnown user ID from your auth system. Enables cross-device tracking
timeoutnumberNo5000API call timeout in milliseconds
quietbooleanNofalseSuppress [SplitWisp] console warnings
applyVisualChangesbooleanNotrueAuto-apply visual changes from experiment variants to the DOM
visualChangeTimeoutnumberNo5000Maximum time (ms) to wait for late-loading DOM elements when applying visual changes. Uses a MutationObserver for SPA compatibility

API Reference

tracker.init(): Promise<Assignment[]>

Fetches variant assignments for all active experiments for this session. Call once on page load. Results are cached in-memory for the page lifetime.

When applyVisualChanges is enabled (the default), visual changes from all assigned variants are automatically applied to the DOM using a MutationObserver that handles dynamically rendered elements.

After assignments are fetched, conversion goals configured on experiments are automatically set up — no additional code required.

Any events queued offline from previous page loads are drained and retried.

Returns: An array of Assignment objects.

tracker.getVariant(experimentId): Assignment | null

Returns the assigned variant for a specific experiment. Returns null if the experiment was not found or the session was not assigned.

const variant = tracker.getVariant('exp_hero_headline');
if (variant) {
  console.log(variant.variantId);    // e.g. "variant_a"
  console.log(variant.variantName);  // e.g. "Green Button"
  console.log(variant.changes);      // visual changes array (if any)
}

tracker.getAssignments(): Assignment[]

Returns all current variant assignments as an array.

tracker.track(opts): Promise<void>

Tracks an event (impression, conversion, click, etc.). Failed calls are queued to localStorage and retried on the next init().

OptionTypeRequiredDescription
eventstringYesEvent name: "impression", "conversion", "pageview", or any custom string
experimentIdstringNoExperiment this event relates to. The SDK automatically attaches the assigned variantId
valuenumberNoNumeric value (e.g. revenue in cents)
propertiesobjectNoArbitrary metadata object

UTM parameters (utm_source, utm_medium, utm_campaign) are automatically captured from the URL and attached to all track calls for source attribution.

tracker.trackConversion(experimentId, value?, properties?): Promise<void>

Convenience wrapper that sends a "conversion" event for a specific experiment.

// Revenue conversion (value in cents)
tracker.trackConversion('exp_pricing_cta', 9900); // $99.00

// Simple binary conversion
tracker.trackConversion('exp_signup_flow');

// Conversion with metadata
tracker.trackConversion('exp_checkout', 14999, {
  plan: 'pro',
  coupon: 'SAVE20',
});

tracker.trackBeacon(opts): void

Fire-and-forget tracking via the browser's sendBeacon API. Does not retry on failure. Use for page unload or visibility change events:

document.addEventListener('visibilitychange', () => {
  if (document.visibilityState === 'hidden') {
    tracker.trackBeacon({ event: 'pagehide' });
  }
});

tracker.autoTrackPageViews(): () => void

Automatically tracks "pageview" events on the current page and on SPA navigations (via pushState, replaceState, and popstate). Also tracks "pagehide" via sendBeacon when the tab is hidden.

Returns: A teardown function to stop tracking.

const stopTracking = tracker.autoTrackPageViews();

// Later, to clean up:
stopTracking();

tracker.identify(userId): void

Associate a known user ID with the current session. Call before or after init(). Enables cross-device attribution when a visitor logs in.

// After user signs in
tracker.identify('user_12345');

tracker.sessionId: string

Read-only property returning the current session ID.

Assignment Object

Each assignment returned by init() or getVariant() has this shape:

interface Assignment {
  experimentId: string;       // e.g. "exp_hero_headline"
  variantId: string;          // e.g. "variant_a"
  variantName: string;        // e.g. "Green Button"
  changes?: VisualChange[];   // visual changes to apply (if any)
  conversionGoals?: ConversionGoal[];  // auto-tracking goals (if configured)
}

Visual changes are applied automatically unless applyVisualChanges: false is set. Conversion goals are tracked automatically with per-session deduplication. See Conversion Goals for details on the five supported goal types.

Anti-Flicker (Visual Changes)

When using visual A/B tests created with the Visual Editor or the dashboard, add this CSS to prevent a flash of original content:

<style>
  .sw-cloak { opacity: 0 !important; }
</style>
<body class="sw-cloak">

The SDK automatically removes the sw-cloak class after visual changes are applied — or after a safety timeout (default 5 seconds) to guarantee the page always becomes visible, even if init() fails.

Offline Queue & Resilience

The SDK is designed to fail gracefully:

  • Offline queue — failed track() calls are saved to localStorage (capped at 100 events) and retried on the next init() call
  • Timeout handling — all API calls abort after the configured timeout (default 5s)
  • Blocked sessions — if the server returns 429 (quota exceeded), the SDK caches the blocked state in sessionStorage for 1 hour and suppresses further requests
  • Private browsing — gracefully falls back to in-memory session IDs when localStorage is unavailable

Architecture

Browser → tracker.umd.js (~3KB gzip)
  ├─ GET  /v1/assign?apiKey=...&sessionId=...  → variant assignments + visual changes + goals
  ├─ POST /v1/track  { event, sessionId, ... } → event ingestion
  ├─ localStorage: _sw_sid (session ID), _sw_q (offline queue)
  └─ sessionStorage: _sw_utm (UTM params), _sw_goals (fired goals), _sw_blocked (rate limit)

What's Next?