SDK overview: web and apps

X3 measures websites and native apps with one family of SDKs. They share one model: a session is a visit, a pageview is a page on the web and a screen view in an app, events are attached to a pageview or screen view, identify links a session to a person by hashes, a lead is an explicit inquiry, and purchases become conversions only when a server confirms them. Everything lands in the same project, so a journey can start on your website and end in your app.

SDKPackagePlatformsGuide
Web SDKnpm @x3growth/sdk (script tag x3.js)every browser, React, Next.js, any bundler, server helperWeb SDK
iOS SDKSwift Package X3Kit (CocoaPods X3Kit)iOS 15+iOS SDK (Swift)
Android SDKMaven io.x3growth:sdkAndroid 7 (API 24)+Android SDK (Kotlin)
React Native and Exponpm @x3growth/react-nativeiOS and Android through the native SDKsReact Native and Expo
Flutterpub.dev x3growthiOS and Android through the native SDKsFlutter

All SDKs talk to the X3 API directly or, recommended, through a first-party proxy on your own domain (https://your-domain/n3). Publishable keys (pk_3x_…) are public; secret keys (sk_3x_…) belong on your server only (keys).

The web SDK follows the project's consent mode; the app SDKs make you choose a consent mode in code (always, opt_in or opt_out).

You decide how consent works in your app, and you are responsible for that decision. X3 implements your choice technically but does not check whether it is legally right for your users and countries. Clarify the legal basis with your privacy counsel and describe X3 in your privacy policy and in the App Store and Google Play privacy details.

Web SDK

@x3growth/sdk is the browser tracker plus adapters, a first-party proxy handler, a server helper and a DOM-free pixel transport. The same API exists in three forms:

  • ESM exports: import { init, track } from "@x3growth/sdk" (npm package);
  • the x3 object of the script-tag build: x3.track(…) (script tag);
  • queue commands before the script has loaded: x3("track", "signup_started", { plan: "pro" }).

Every browser method swallows its own errors; tracking never breaks your page. The detailed behaviour of each method is in the SDK reference.

Entry points

Entry pointWhat it containsGuide
@x3growth/sdkthe tracker: init, track, goal, funnelStep, page, identify, conversion, lead, consent, flush, getIds, optOut, optIn, the object x3 and VERSIONSDK reference
@x3growth/sdk/reactX3Provider, useX3, useTrackOnMount, experiments: X3Experiments, useVariant, useExpose, VariantReact, experiments
@x3growth/sdk/nextX3Analytics, fetchClientConfig, getAssignments, getVariant, QUEUE_STUB, inlineJson, earlyLoaderNext.js, experiments in Next.js
@x3growth/sdk/next/proxycreateX3ProxyHandler and its helpers proxiedPath, visitorIp, visitorCountry, visitorGeo, visitorCookie, visitorSetCookie, VISITOR_COOKIENext.js proxy
@x3growth/sdk/servercreateX3Server, requestContext, clientIpFrom, cookieValue, fetchAssignments, X3ServerErrorServer helper
@x3growth/sdk/pluginsplugin factories for bundlers: video, sections, forms, identify, consent, bookings, journeys, experiments, plus CMP_ADAPTERSnpm package › Plugins
@x3growth/sdk/presetsbridges for existing funnel code: bridgeFunnelStage, bridgeQuizAnswer, bridgeFunnelEvent, listenQuizSubmitted, listenBookings, reportBooking, newEventId, bookingEventId, bridgeCmp, CMP_ADAPTERSCustom events and presets
@x3growth/sdk/pixelcreateX3Pixel, shopifyPixel for sandboxes without a DOMnpm package › Pixel transport

Tracker functions

FunctionSignatureWhat it does
initinit(options: X3InitOptions): voidStarts the tracker once per page. Options: key (required), host (API origin or proxy path, default /n3), config, consent, autocapture, plugins, hashRouting, test, respectDoNotTrack, debug (options).
tracktrack(name: string, props?: Props, options?: { flush?: boolean }): voidBuilt-in or custom event on the current pageview; name is lowercase snake_case, up to 20 props (events).
goalgoal(name: string, props?: Props): voidtrack plus immediate send, for events you define as goals.
funnelStepfunnelStep(funnel: string, step: string): voidEmits funnel_step { funnel, step }.
pagepage(path?: string): voidManual pageview for routers without the History API.
identifyidentify(data: IdentifyData): Promise<void>Links the visit to a person; email, phone, names, externalId and leadId are hashed in the browser (identify).
conversionconversion(definition: string, options?: ConversionOptions): Promise<string | null>Browser conversion beacon for a definition with trigger beacon; resolves with the event id.
leadlead(data: LeadData): Promise<boolean>A form's lead straight from the browser, when the project allows it (x3.lead()).
consentconsent(state: "granted" | "denied" | { analytics?: boolean; recordings?: boolean }): voidTells X3 the visitor's choice (consent).
privacyx3.privacy(): voidOpens the visitor's privacy panel (privacy center).
flushflush(): voidSends everything queued now.
getIdsgetIds(): X3IdssessionId, pageviewId, attribution, url, test, consent for your server calls.
optOut / optInoptOut(): void, optIn(): voidStores or removes this browser's opt-out.
presetsquiz.view/answer/contact/submit, cta, whatsapp, messaging, calculator, booking, video.track, video.custom, bridges.*Standard preset events (reference).
experimentsvariant(key, options?), experiment(key), expose(key), experiments.ready/onChange/allA/B tests in the browser (experiments).
versionx3.version, VERSIONThe SDK version string.
import { init, track, identify, getIds } from "@x3growth/sdk";

init({ key: "pk_3x_YOUR_SITE_KEY", host: "/n3" });
track("plan_selected", { plan: "year" });
await identify({ email: "jane@example.com", form: "newsletter" });
const { sessionId } = getIds(); // send it with your form post

Server helper

createX3Server({ apiUrl, apiKey, project?, publishableKey?, proxyKey?, timeoutMs?, fetch? }) returns project(), leads.create, leads.addNote, conversions.create, events.track, purchase / purchases.create, refund / purchases.refund, experiments.assign and requestContext. Errors are X3ServerError (RFC 9457 problem). Details: Server helper and shop purchases. App purchases use the same purchase call with the store transaction id (purchases in apps).

Proxy

app/n3/[...path]/route.ts
import { createX3ProxyHandler } from "@x3growth/sdk/next/proxy";

export const { GET, POST, OPTIONS } = createX3ProxyHandler({
  apiOrigin: process.env.X3_API_ORIGIN!,
  proxyKey: process.env.X3_PROXY_KEY,
});

The handler forwards only X3's path families and adds the visitor's IP, country and the proxy key. From @x3growth/sdk 1.3.0 it also forwards the app paths (/v1/m/*), X3 links (/go/<code>) and, through two extra route files, Apple's postback copies; see postback copies and the proxy protocol.

App SDKs

The app SDKs are native: Swift for iOS and Kotlin for Android, with thin wrappers for React Native (an Expo Module with a config plugin) and Flutter (a plugin with Pigeon channels). Every behaviour (consent, queue, sessions, hashing, attribution, recordings) lives in the native cores, so all four APIs use the same names and the same rules (architecture).

PackageCoordinatesVersionContains
iOSSwift Package https://github.com/X3Growth/x3-sdk-ios (products X3Kit, X3KitUI, X3KitReplay), CocoaPods X3Kit (subspecs Core, UI, Replay)1.0.0core, consent UI, wireframe recorder
Androidio.x3growth:sdk, io.x3growth:sdk-replay, io.x3growth:sdk-compose1.0.0core with View dialogs, recorder for Views and Compose, Compose helpers
React Native and Exponpm @x3growth/react-native (config plugin app.plugin.js)1.0.0facade, navigation hooks, consent components, masking components
Flutterpub.dev x3growth1.0.0facade, navigator observer, consent widgets
Webnpm @x3growth/sdk1.3.0browser SDK, proxy handler with app paths

Start with the app quickstart; every call is described in the app SDK reference.

X3.configure(key: "pk_3x_YOUR_APP_KEY", options: X3Options(consent: X3ConsentOptions(mode: .optIn)))
X3.screen("Paywall")
X3.identify(userId: user.id, email: user.email)
X3.track("plan_selected", properties: ["plan": "year"])

Function matrix

Rows are concepts, columns the SDKs. "–" means the concept does not exist on that platform.

ConceptWeb (@x3growth/sdk)iOS (X3Kit)Android (io.x3growth.sdk)React NativeFlutter
startinit({ key, host })X3.configure(key:options:)X3.configure(context, key, options)X3.configure(key, options)X3.configure(key, options)
page or screenautomatic, page(path?)automatic (UIKit), X3.screen(_:properties:), SwiftUI .x3Screen(_:)automatic (activities), X3.screen(name, properties), X3TrackNavigation(navController)X3.screen, useX3NavigationTracking, useX3ExpoRouterTrackingX3.screen, X3NavigatorObserver
eventtrack(name, props?)X3.track(_:properties:)X3.track(name, properties)X3.track(name, properties?)X3.track(name, [properties])
goalgoal(name, props?)X3.track (goals are defined in the dashboard)X3.trackX3.trackX3.track
identifyidentify({ email, externalId, leadId, … })X3.identify(userId:email:leadId:)X3.identify(userId, email, leadId)X3.identify(userId, { email?, leadId? })X3.identify(userId, {email, leadId})
forget the person– (session ends with the tab)X3.reset()X3.reset()X3.reset()X3.reset()
leadlead(data)try await X3.lead(_:)X3.lead(lead) (suspend)X3.lead(lead)X3.lead(lead)
purchaseserver: purchase({ orderId, … })X3.purchase(_:) hint + server confirmationX3.purchase(purchase) hint + server confirmationX3.purchase(purchase)X3.purchase(purchase)
account token for stores–X3.purchaseAccountToken()X3.purchaseAccountToken()X3.purchaseAccountToken()X3.purchaseAccountToken()
consentconsent("granted" | "denied" | { analytics, recordings })X3.consent(_:), X3.consentAll(_:)X3.consent(update), X3.consentAll(granted)X3.consent, X3.consentAllX3.consent, X3.consentAll
consent stategetIds().consentX3.consentState(), X3.onConsentChange(_:)X3.consentState(), X3.addConsentListener(listener)X3.consentState(), X3.onConsentChange(listener)X3.consentState(), X3.consentChanges
opt-outoptOut(), optIn()X3.setOptOut(_:), X3.isOptedOut()X3.setOptOut(optOut), X3.isOptedOut()X3.setOptOut, X3.isOptedOut()X3.setOptOut, X3.isOptedOut()
consent UIyour CMP (adapters)X3ConsentView, X3PrivacySettingsView (X3KitUI)X3ConsentDialog, X3PrivacySettingsDialog, Compose X3ConsentSheet<X3ConsentSheet>, <X3PrivacySettings>X3ConsentSheet.show, X3PrivacySettings
send nowflush()X3.flush()X3.flush()X3.flush()X3.flush()
ids for your servergetIds()X3.installId, X3.sessionIdX3.installId, X3.sessionIdX3.ids()X3.ids()
deep links and X3 links– (URL parameters are read automatically)X3.handleOpenURL(_:), X3.applyLinkToken(_:)X3.handleOpenURL(uri), X3.handleIntent(intent), X3.applyLinkToken(token)automatic, X3.handleOpenURL(url)automatic, X3.handleOpenURL(uri)
ATT prompt–X3.requestTrackingAuthorization()–X3.requestTrackingAuthorization() (Android: "unavailable")X3.requestTrackingAuthorization()
recordingsx3.replay.js (recordings)X3KitReplay, recording: .wireframeio.x3growth:sdk-replayrecording: { mode: "wireframe" }see Flutter › Recordings
maskingdata-x3-* and project rulesx3Masking, .x3Mask(), .x3Unmask()X3.setMasking, Modifier.x3Mask()<X3Mask>, <X3Unmask>– (1.0.0 records the Flutter view as one masked area)
experimentsvariant, useVariant, getAssignments––––
versionx3.version, VERSIONX3.versionX3.VERSIONX3.versionX3.version