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.
| SDK | Package | Platforms | Guide |
|---|---|---|---|
| Web SDK | npm @x3growth/sdk (script tag x3.js) | every browser, React, Next.js, any bundler, server helper | Web SDK |
| iOS SDK | Swift Package X3Kit (CocoaPods X3Kit) | iOS 15+ | iOS SDK (Swift) |
| Android SDK | Maven io.x3growth:sdk | Android 7 (API 24)+ | Android SDK (Kotlin) |
| React Native and Expo | npm @x3growth/react-native | iOS and Android through the native SDKs | React Native and Expo |
| Flutter | pub.dev x3growth | iOS and Android through the native SDKs | Flutter |
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
x3object 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 point | What it contains | Guide |
|---|---|---|
@x3growth/sdk | the tracker: init, track, goal, funnelStep, page, identify, conversion, lead, consent, flush, getIds, optOut, optIn, the object x3 and VERSION | SDK reference |
@x3growth/sdk/react | X3Provider, useX3, useTrackOnMount, experiments: X3Experiments, useVariant, useExpose, Variant | React, experiments |
@x3growth/sdk/next | X3Analytics, fetchClientConfig, getAssignments, getVariant, QUEUE_STUB, inlineJson, earlyLoader | Next.js, experiments in Next.js |
@x3growth/sdk/next/proxy | createX3ProxyHandler and its helpers proxiedPath, visitorIp, visitorCountry, visitorGeo, visitorCookie, visitorSetCookie, VISITOR_COOKIE | Next.js proxy |
@x3growth/sdk/server | createX3Server, requestContext, clientIpFrom, cookieValue, fetchAssignments, X3ServerError | Server helper |
@x3growth/sdk/plugins | plugin factories for bundlers: video, sections, forms, identify, consent, bookings, journeys, experiments, plus CMP_ADAPTERS | npm package › Plugins |
@x3growth/sdk/presets | bridges for existing funnel code: bridgeFunnelStage, bridgeQuizAnswer, bridgeFunnelEvent, listenQuizSubmitted, listenBookings, reportBooking, newEventId, bookingEventId, bridgeCmp, CMP_ADAPTERS | Custom events and presets |
@x3growth/sdk/pixel | createX3Pixel, shopifyPixel for sandboxes without a DOM | npm package › Pixel transport |
Tracker functions
| Function | Signature | What it does |
|---|---|---|
init | init(options: X3InitOptions): void | Starts 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). |
track | track(name: string, props?: Props, options?: { flush?: boolean }): void | Built-in or custom event on the current pageview; name is lowercase snake_case, up to 20 props (events). |
goal | goal(name: string, props?: Props): void | track plus immediate send, for events you define as goals. |
funnelStep | funnelStep(funnel: string, step: string): void | Emits funnel_step { funnel, step }. |
page | page(path?: string): void | Manual pageview for routers without the History API. |
identify | identify(data: IdentifyData): Promise<void> | Links the visit to a person; email, phone, names, externalId and leadId are hashed in the browser (identify). |
conversion | conversion(definition: string, options?: ConversionOptions): Promise<string | null> | Browser conversion beacon for a definition with trigger beacon; resolves with the event id. |
lead | lead(data: LeadData): Promise<boolean> | A form's lead straight from the browser, when the project allows it (x3.lead()). |
consent | consent(state: "granted" | "denied" | { analytics?: boolean; recordings?: boolean }): void | Tells X3 the visitor's choice (consent). |
privacy | x3.privacy(): void | Opens the visitor's privacy panel (privacy center). |
flush | flush(): void | Sends everything queued now. |
getIds | getIds(): X3Ids | sessionId, pageviewId, attribution, url, test, consent for your server calls. |
optOut / optIn | optOut(): void, optIn(): void | Stores or removes this browser's opt-out. |
| presets | quiz.view/answer/contact/submit, cta, whatsapp, messaging, calculator, booking, video.track, video.custom, bridges.* | Standard preset events (reference). |
| experiments | variant(key, options?), experiment(key), expose(key), experiments.ready/onChange/all | A/B tests in the browser (experiments). |
version | x3.version, VERSION | The 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 postServer 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
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).
| Package | Coordinates | Version | Contains |
|---|---|---|---|
| iOS | Swift Package https://github.com/X3Growth/x3-sdk-ios (products X3Kit, X3KitUI, X3KitReplay), CocoaPods X3Kit (subspecs Core, UI, Replay) | 1.0.0 | core, consent UI, wireframe recorder |
| Android | io.x3growth:sdk, io.x3growth:sdk-replay, io.x3growth:sdk-compose | 1.0.0 | core with View dialogs, recorder for Views and Compose, Compose helpers |
| React Native and Expo | npm @x3growth/react-native (config plugin app.plugin.js) | 1.0.0 | facade, navigation hooks, consent components, masking components |
| Flutter | pub.dev x3growth | 1.0.0 | facade, navigator observer, consent widgets |
| Web | npm @x3growth/sdk | 1.3.0 | browser 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.
| Concept | Web (@x3growth/sdk) | iOS (X3Kit) | Android (io.x3growth.sdk) | React Native | Flutter |
|---|---|---|---|---|---|
| start | init({ key, host }) | X3.configure(key:options:) | X3.configure(context, key, options) | X3.configure(key, options) | X3.configure(key, options) |
| page or screen | automatic, page(path?) | automatic (UIKit), X3.screen(_:properties:), SwiftUI .x3Screen(_:) | automatic (activities), X3.screen(name, properties), X3TrackNavigation(navController) | X3.screen, useX3NavigationTracking, useX3ExpoRouterTracking | X3.screen, X3NavigatorObserver |
| event | track(name, props?) | X3.track(_:properties:) | X3.track(name, properties) | X3.track(name, properties?) | X3.track(name, [properties]) |
| goal | goal(name, props?) | X3.track (goals are defined in the dashboard) | X3.track | X3.track | X3.track |
| identify | identify({ 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() |
| lead | lead(data) | try await X3.lead(_:) | X3.lead(lead) (suspend) | X3.lead(lead) | X3.lead(lead) |
| purchase | server: purchase({ orderId, … }) | X3.purchase(_:) hint + server confirmation | X3.purchase(purchase) hint + server confirmation | X3.purchase(purchase) | X3.purchase(purchase) |
| account token for stores | – | X3.purchaseAccountToken() | X3.purchaseAccountToken() | X3.purchaseAccountToken() | X3.purchaseAccountToken() |
| consent | consent("granted" | "denied" | { analytics, recordings }) | X3.consent(_:), X3.consentAll(_:) | X3.consent(update), X3.consentAll(granted) | X3.consent, X3.consentAll | X3.consent, X3.consentAll |
| consent state | getIds().consent | X3.consentState(), X3.onConsentChange(_:) | X3.consentState(), X3.addConsentListener(listener) | X3.consentState(), X3.onConsentChange(listener) | X3.consentState(), X3.consentChanges |
| opt-out | optOut(), optIn() | X3.setOptOut(_:), X3.isOptedOut() | X3.setOptOut(optOut), X3.isOptedOut() | X3.setOptOut, X3.isOptedOut() | X3.setOptOut, X3.isOptedOut() |
| consent UI | your CMP (adapters) | X3ConsentView, X3PrivacySettingsView (X3KitUI) | X3ConsentDialog, X3PrivacySettingsDialog, Compose X3ConsentSheet | <X3ConsentSheet>, <X3PrivacySettings> | X3ConsentSheet.show, X3PrivacySettings |
| send now | flush() | X3.flush() | X3.flush() | X3.flush() | X3.flush() |
| ids for your server | getIds() | X3.installId, X3.sessionId | X3.installId, X3.sessionId | X3.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() |
| recordings | x3.replay.js (recordings) | X3KitReplay, recording: .wireframe | io.x3growth:sdk-replay | recording: { mode: "wireframe" } | see Flutter › Recordings |
| masking | data-x3-* and project rules | x3Masking, .x3Mask(), .x3Unmask() | X3.setMasking, Modifier.x3Mask() | <X3Mask>, <X3Unmask> | – (1.0.0 records the Flutter view as one masked area) |
| experiments | variant, useVariant, getAssignments | – | – | – | – |
| version | x3.version, VERSION | X3.version | X3.VERSION | X3.version | X3.version |