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 Native | 0.76 or later |
| iOS | 15.1 or later |
| Android | API 24 or later |
| Dependencies | none |
Installation
npm install intempt-react-nativecd ios && pod installAutolinking 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',
});| Field | Type | Description |
|---|---|---|
apiKey | string | API key in prefix.secret form |
orgId | string | Organization identifier |
projectId | string | Project identifier |
sourceId | string | Source the events are attributed to |
instanceName | string | Optional. 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 eventsUse 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.
Consent
import { ConsentAction } from 'intempt-react-native';
await intempt.consent(ConsentAction.Accept, 1798761600, {
email: 'ada@example.com',
category: 'marketing',
});| Argument | Type | Description |
|---|---|---|
action | ConsentAction | Accept or Reject |
validUntil | number | Unix seconds the decision is valid until |
email | string | Optional |
message | string | Optional |
category | string | Optional |
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.
Rejectopts the user out andAcceptopts them in. You do not need a separateoptOut()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,
});| Option | Default | Emits |
|---|---|---|
sessions | on | Session start and end, with device attributes |
versionChanges | off | Application Installed / Application Updated, once per version |
appStateChanges | off | Application 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
}
}| Code | Meaning |
|---|---|
malformed_api_key | Key is not in prefix.secret form |
missing_configuration | An identifier was blank |
invalid_property_value | A property value could not be represented |
missing_identity | A required identifier was absent for the event type |
encoding_failed | The payload could not be serialized |
terminal | Will not succeed on retry |
retryable | Retry with backoff |
transport | Network layer failed |
storage_unavailable | The queue could not persist |
server | Intempt rejected the request with detail |
not_initialized | Called before init() |
unknown | Native 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';