Intempt Docs
Developer DocsSDK

React Native SDK

Track events, identify users, manage consent and run personalization in React Native apps on iOS and Android.

React Native SDK

The Intempt React Native SDK wraps the native iOS and Android SDKs behind one JavaScript API. Events, identity, consent, delivery and personalization all run natively — this package is the bridge, not a second implementation.

Not yet published to npm. The JavaScript layer and both native modules are complete, but the iOS SDK is not yet distributed through CocoaPods and the Android SDK reaches full API parity in version 3.0. This page documents the shipping API so integrations can be written against it.

Requirements

React Native0.76 or later
iOS15.1 or later
AndroidAPI 24 or later
Dependenciesnone

Installation

npm install intempt-react-native
cd ios && pod install

Autolinking registers both native modules. No manual linking, and nothing to add to MainApplication or AppDelegate.

Initialization

Credentials are passed in code. Call init() once, as early as possible — before the first screen renders, so session tracking starts at app launch rather than at first navigation.

import { init } from 'intempt-react-native';

const intempt = await init({
  apiKey: 'yourPrefix.yourSecret',
  orgId: 'your-org',
  projectId: 'your-project',
  sourceId: 'your-source',
});
FieldTypeDescription
apiKeystringAPI key in prefix.secret form
orgIdstringOrganization identifier
projectIdstringProject identifier
sourceIdstringSource the events are attributed to
instanceNamestringOptional. Defaults to "default"

init() resolves to an instance. Every method on it returns a Promise.

Android SDK below 3.0 ignores these credentials. It reads them from android/app/src/main/assets/intempt-config.json instead, and init() rejects with missing_configuration when that file is absent. Runtime credentials arrive in Android SDK 3.0. On iOS the arguments above are the only path.

Multiple instances

Each named instance has its own credentials, queue and identity.

iOS only for now. The Android SDK is a singleton, so any instanceName other than "default" rejects with unsupported_on_android.

const eu = await init({ ...config, instanceName: 'eu' });
const us = await init({ ...config, instanceName: 'us' });

Tracking events

await intempt.track('Viewed pricing', { source: 'nav', seats: 3, trial: false });

Property values may be strings, numbers, booleans, null, Date, arrays or nested objects.

On iOS, numbers and booleans stay numbers and booleans. On Android below 3.0 they are stringified — the native SDK accepts Map<String, String> only, so {seats: 3} arrives as "3" and nested maps arrive JSON-encoded. Typed values arrive with Android SDK 3.0; the conversion is centralised in one file so it stops in one change.

What the return value means

track() resolves to whether the event was accepted into the queue, not whether it reached Intempt.

const queued = await intempt.track('Signed up');

false means the event was dropped before queueing — the user is opted out, a property could not be represented, encoding failed, or storage was unavailable.

On Android this is always true today. The native SDK returns no acceptance signal below 3.0, so the bridge cannot forward one. Treat the value as meaningful on iOS and as a placeholder on Android until 3.0.

To confirm delivery, flush and read the count:

const delivered = await intempt.flush();

Identifying users

await intempt.identify('user-123', {
  userAttributes: { email: 'ada@example.com', plan: 'pro' },
});

Associate a profile with an account:

await intempt.group('acct-9', {
  accountAttributes: { tier: 'enterprise', seats: 40 },
});

Merge two identities:

await intempt.alias('user-123', 'anon-abc');

Record an event against a user, an account, or both:

await intempt.record('Renewed', {
  userId: 'user-123',
  accountId: 'acct-9',
  data: { mrr: 120 },
});

Reading the current identity

const profileId = await intempt.getProfileId();
const sessionId = await intempt.getSessionId();

Ending a session

await intempt.logOut();   // rotate the anonymous identity, keep queued events
await intempt.reset();    // rotate the identity AND discard queued events

Use logOut() when a user signs out on a device others may use — it stops the next person inheriting the previous identity. Use reset() when queued events should not be sent at all.

Commerce events

await intempt.productView('sku-1');
await intempt.productAdd('sku-1', 2);

await intempt.productOrdered([
  { productId: 'sku-1', quantity: 2 },
  { productId: 'sku-2', quantity: 1 },
]);

An entry missing productId or quantity fails the whole call rather than being skipped, so a dropped line item never quietly changes an order total.

import { ConsentAction } from 'intempt-react-native';

await intempt.consent(ConsentAction.Accept, 1798761600, {
  email: 'ada@example.com',
  category: 'marketing',
});
ArgumentTypeDescription
actionConsentActionAccept or Reject
validUntilnumberUnix seconds the decision is valid until
emailstringOptional
messagestringOptional
categorystringOptional

Three behaviours worth knowing:

  • Consent is transmitted even when the user is opted out. A withdrawal has to reach Intempt.
  • It is sent immediately to its own endpoint, not batched with events.
  • Reject opts the user out and Accept opts them in. You do not need a separate optOut() call.

Opting users out

await intempt.optOut();
await intempt.optIn();

const optedOut = await intempt.hasOptedOut();

optOut() stops collection and discards events already queued. Events gathered before someone objected are not uploaded afterwards. Queued consent records are kept — they are the record of the decision itself.

Delivery

const delivered = await intempt.flush();

await intempt.setFlushInterval(30);   // seconds; 0 disables the timer
const interval = await intempt.getFlushInterval();

Automatic events

await intempt.setAutomaticEvents({
  sessions: true,
  versionChanges: false,
  appStateChanges: false,
});
OptionDefaultEmits
sessionsonSession start and end, with device attributes
versionChangesoffApplication Installed / Application Updated, once per version
appStateChangesoffApplication Opened / Application Backgrounded on every transition

Only sessions are on by default. Turn the others on deliberately — appStateChanges in particular fires on every foreground and background transition.

Recommendations

const products = await intempt.products({
  feedId: 'homepage-feed',
  count: 10,
  fields: ['productId', 'title', 'price', 'imageUrl', 'url'],
});

Experiment and personalization assignment is not part of the mobile SDKs — it is an intemptjs capability. Recommendation feeds are a different thing and are here.

fields defaults to a compact set on purpose, and you should widen it deliberately rather than omit it. A request with no fields returns every catalog column, including raw ML embedding vectors — for the same ten products that is 222,919 bytes against 503, roughly 443 times the payload, over whatever connection the device happens to have.

Push notifications

await intempt.setPushToken(hexToken);
await intempt.trackPushOpen(notification.data);
await intempt.trackPushReceived(notification.data);

iOS — pass the APNs device token as a hex string. Binary data has no representation across the React Native bridge.

Android — token registration requires Google Play Services. An emulator running the default system image does not have them, and registration fails there in a way that is hard to read. Use a google_apis image when testing push.

Error handling

Every rejection is an IntemptError carrying a code.

import { IntemptError, IntemptErrorCode } from 'intempt-react-native';

try {
  await intempt.track('Checkout started');
} catch (error) {
  if (error instanceof IntemptError && error.isRetryable) {
    // transport failure or a 5xx; error.retryAfter may be set
  }
}
CodeMeaning
malformed_api_keyKey is not in prefix.secret form
missing_configurationAn identifier was blank
invalid_property_valueA property value could not be represented
missing_identityA required identifier was absent for the event type
encoding_failedThe payload could not be serialized
terminalWill not succeed on retry
retryableRetry with backoff
transportNetwork layer failed
storage_unavailableThe queue could not persist
serverIntempt rejected the request with detail
not_initializedCalled before init()
unknownNative returned a code this package version does not recognise — the two have drifted

A 401 is classified terminal, not retryable — a bad credential cannot start working on retry. Queued events are kept, because the data is fine and the credentials are what need fixing.

Platform differences

A method that a platform does not support yet rejects with unsupported_on_android or unsupported_on_ios and names the method. It never resolves silently.

if (error.isUnsupported) {
  // present on the contract, not on this platform yet
}

Until Android SDK 3.0, these reject on Android:

reset · getProfileId · getSessionId · flush · getFlushInterval · setFlushInterval · products · getAutomaticEvents · setAutomaticEvents · the whole autocapture object (configure, start, stop, isRunning) · setPushToken · trackPushOpen · trackPushReceived · init() with any instanceName other than "default".

TypeScript

Types ship with the package; nothing extra to install.

import type {
  IntemptConfig,
  IntemptProperties,
  ProductRecommendation,
} from 'intempt-react-native';

On this page