> For the complete AppRefer docs index, see https://apprefer.com/llms.txt (every page as markdown) or https://apprefer.com/llms-full.txt (all docs in one file).

# React Native SDK

TypeScript SDK for attribution, product events, SKAdNetwork and AdAttributionKit conversion values, AppRefer Billing and the in-app Support chat, on iOS and Android.

Every page in this section follows the same order, so you can switch between the [iOS](https://apprefer.com/docs/sdk/ios.md), [Android](https://apprefer.com/docs/sdk/android.md), [Flutter](https://apprefer.com/docs/sdk/flutter.md) and [React Native](https://apprefer.com/docs/sdk/react-native.md) references and find the same thing in the same place. All four SDKs share one version number; this page documents **0.13.1**.

## Install

Install the tagged GitHub release (the package isn't on the npm registry) together with its AsyncStorage peer, then install the pods. Git installs build the JavaScript and type declarations automatically.

```bash
npm install github:AppAgentic/apprefer-react-native-sdk#0.13.1 @react-native-async-storage/async-storage
cd ios && pod install
```

```typescript
import { AppRefer } from '@apprefer/react-native-sdk';
import { AppReferBilling } from '@apprefer/react-native-sdk/billing'; // only if you use AppRefer Billing
```

> **Pre-1.0 versions**
>
> Before 1.0, a minor version can change behaviour. Keep the install pinned to a tag, read the changelog highlights below, and run `pod install` and rebuild the native app after every upgrade.

### Platform setup

-   The SDK ships native modules (the `AppRefer` pod on iOS and an autolinked Android package) for the ATT reader, AdServices, the Install Referrer, push environment and the Support chat. Expo apps need a development build, not Expo Go.
-   **iOS:** iOS 15 or later. In your privacy manifest (`PrivacyInfo.xcprivacy`), declare **only** `trk.apprefer.com` under `NSPrivacyTrackingDomains`, never `apprefer.com`. For postback copies, add the Info.plist keys in [SKAdNetwork and AdAttributionKit](#skadnetwork-and-adattributionkit).
-   **Android:** minimum API 23 (for the Support module). The SDK declares only `INTERNET` and never reads the Advertising ID, so it adds no `AD_ID` permission.

**ios/YourApp/PrivacyInfo.xcprivacy**

```xml
<key>NSPrivacyTracking</key>
<true/>
<key>NSPrivacyTrackingDomains</key>
<array>
    <string>trk.apprefer.com</string>
</array>
```

## Configure

Call `configure` once, as early as possible (your root layout or `App` component). On the first launch it sends the install's signals to AppRefer and resolves attribution; on later launches it returns the cached result. Repeat calls reuse the existing instance.

**App.tsx**

```typescript
import { AppRefer } from '@apprefer/react-native-sdk';

useEffect(() => {
  AppRefer.configure({ apiKey: 'pk_live_...', debug: __DEV__ }).then((attribution) => {
    // attribution?.network, attribution?.matchType
  });
}, []);
```

| Option | Type | Default | Description |
| --- | --- | --- | --- |
| `apiKey` | string | required | SDK key (`pk_live_…` or `pk_test_…`) |
| `userId` | string | undefined | Your app's user id, if someone is already signed in |
| `debug` | boolean | false | Console logs |
| `logLevel` | number | 1 | 0 none, 1 errors, 2 warnings, 3 verbose |

`configure` resolves to `Attribution | null` and never rejects. Methods called before it do nothing.

> **Live and test keys**
>
> The server picks the environment from the key. `pk_test_` installs, events and purchases are sandbox data: visible in the dashboard's sandbox view and Debugger, never forwarded to ad networks. Use `pk_live_` in release builds.

### configureResult

`configureResult` takes the same options and tells you why there is or isn't an attribution. It reports organic only after the server has said so, never because the network failed.

```typescript
const result = await AppRefer.configureResult({ apiKey: 'pk_live_...' });
switch (result.status) {
  case 'attributed': showWelcome(result.attribution.campaignName); break;
  case 'pending': break;  // waiting for the user's ATT answer (iOS)
  case 'organic': break;  // the server answered: no ad click matched
  case 'failed': console.warn(result.error); break;
}
```

| Status | Meaning |
| --- | --- |
| `'attributed'` | Matched; `result.attribution` is set |
| `'pending'` | The server is holding the decision until the user answers the ATT prompt (up to 24 hours) |
| `'organic'` | Confirmed organic install |
| `'failed'` | No answer and nothing cached; `result.error` is set |

`AppRefer.getAttribution()` returns the cached attribution synchronously, without a network call.

## Attribution result

The `Attribution` object returned by `configure` and `getAttribution()`:

| Property | Type | Description |
| --- | --- | --- |
| `network` | string | Ad network, e.g. `meta`, `google`, `tiktok`, `apple_ads`, `openai`, or `organic` |
| `matchType` | string | How the install was matched, e.g. `adservices`, `install_referrer`, `ip_match`, `probabilistic`, `organic` (see [Attribution](https://apprefer.com/docs/attribution.md)) |
| `attributionId` | string? | AppRefer's id for this attribution |
| `campaignId` | string? | Campaign id from the ad network or tracking link |
| `campaignName` | string? | Campaign name |
| `adGroupId` | string? | Ad set / ad group id |
| `adId` | string? | Ad id |
| `keyword` | string? | Search keyword |
| `fbclid` | string? | Meta click id, when matched to a Meta click |
| `gclid` | string? | Google click id |
| `ttclid` | string? | TikTok click id |
| `campaign` | string? | Older field kept for compatibility; use `campaignName` |
| `trackingDecisionPending` | boolean? | True while the server waits for the consent decision |

### Deep links

The SDK reads `ar_click_id` from the launch URL on its own. For links that open an already running app, extract it yourself:

```typescript
import { AppReferDeepLinks } from '@apprefer/react-native-sdk';

const clickId = AppReferDeepLinks.extractClickId(url);
```

## Tracking consent

AppRefer keeps cross-company tracking and first-party work apart for each install. Granted consent uses `trk.apprefer.com` and allows matching to the ad click, the IDFA, advanced matching and forwarding to ad networks. Denied or undetermined consent uses `apprefer.com` only: Apple Ads or organic attribution, analytics, Billing and Support, with nothing forwarded. Details: [Privacy](https://apprefer.com/docs/privacy.md).

-   **iOS:** the SDK reads the App Tracking Transparency status your app already has and **never shows the prompt**. If you show it, call `trackingConsentDidChange()` straight after. It also re-checks on every `configure` and whenever the app becomes active. An undetermined install is held for up to 24 hours in case the user allows tracking.
-   **Android:** pass your app's own choice to `setTrackingConsent`. It is saved across launches; `null` (the default) keeps the legacy behaviour, where requests carry no consent value and are treated as allowed.

```typescript
// iOS: right after your own ATT prompt
await AppRefer.trackingConsentDidChange();

// Android: your consent flow's answer (true, false, or null for legacy)
await AppRefer.setTrackingConsent(false);
```

## User identity

Tell AppRefer who is signed in, so Support conversations and product events carry the account (`app_user_id`). Use the same id you pass to `AppReferBilling.logIn`. You can also pass it as `userId` to `configure`.

```typescript
await AppRefer.setUserId(currentUser.id);
await AppReferBilling.logIn(currentUser.id); // if you use AppRefer Billing
```

### Advanced matching

Send hashed customer details to improve Meta match rates. Values are SHA-256 hashed on the device and dropped unless tracking consent is granted (on Android, also when not set). Call it once after sign-up or sign-in.

```typescript
await AppRefer.setAdvancedMatching({
  email: 'user@example.com',
  phone: '+15551234567',
  firstName: 'Jane',
  lastName: 'Doe',
  dateOfBirth: '19900131', // YYYYMMDD
});
```

### Device id

`await AppRefer.getDeviceId()` returns AppRefer's install id. AppRefer Billing sends it with purchases automatically. If you take payments through RevenueCat instead, set it as the `appreferId` subscriber attribute (see [RevenueCat](https://apprefer.com/docs/integrations/revenuecat.md)).

## Events

Track the in-app moments that matter to you (sign-up, onboarding finished, first workout). Don't track purchases here: AppRefer Billing records purchases, renewals and refunds and forwards them for you. Which events reach ad networks is covered in [Events](https://apprefer.com/docs/events.md).

```typescript
await AppRefer.trackEvent('signup');
await AppRefer.trackEvent('tutorial_complete', { properties: { step: 'final' } });

// Need to know what happened to it?
const result = await AppRefer.trackEventResult('level_up', { properties: { level: 3 } });
// 'sent', 'queued' or 'dropped'
```

| Argument | Type | Description |
| --- | --- | --- |
| `eventName` | string | Event name, e.g. `signup` |
| `options.properties` | Record<string, unknown> | JSON-compatible metadata |
| `options.revenue` | number | Amount for revenue events you track yourself |
| `options.currency` | string | ISO 4217 code, e.g. `USD` |

### Offline queue

-   Events are saved in AsyncStorage before they are sent and keep one stable event id, so delivery is **at least once**.
-   The queue holds up to 1,000 events for up to seven days; the oldest are dropped first.
-   Network errors, HTTP 408, 429 and 5xx are retried with backoff (up to an hour, or longer if the server asks), at launch and when the app returns to the foreground. Other 4xx answers are final.
-   `await AppRefer.clearPendingEvents()` cancels event requests and empties the queue, for example when the user withdraws analytics consent.

## Collection control

`setCollectionEnabled(false)` stops attribution, consent reports, conversion values and product events, cancels what is in flight and clears the event queue. The choice is saved across launches; it never throws. Billing and Support keep working, without the install's device id.

```typescript
await AppRefer.setCollectionEnabled(false); // consent withdrawn

// Later, to resume: enable, then configure again
await AppRefer.setCollectionEnabled(true);
await AppRefer.configure({ apiKey: 'pk_live_...' });
```

Enabling alone sends nothing. Calling it with `true` while collection is already on changes nothing, so it is safe to call from a consent tool on every launch.

## Account switching

Call `reset()` when a user signs out, and await it before the next account signs in. It revokes the old Support session and its push token, clears the Support profile, history and drafts, the user id and queued events. The install's own attribution and device id stay.

```typescript
// Sign out
await AppReferBilling.logOut(); // if you use AppRefer Billing
await AppRefer.reset();

// Next account signs in
await AppRefer.configure({ apiKey: 'pk_live_...', userId: nextUser.id });
await AppReferBilling.logIn(nextUser.id);
```

-   After `reset()`, attribution and events are paused until you call `configure` again. Events tracked in between are dropped, never sent later.
-   Billing logout stays explicit. Until Billing's `logIn` or `logOut` runs, the Billing user who was current at reset gets no install device id, even after a relaunch (0.9.3+).
-   `reset()` is safe to call before `configure` and more than once. It doesn't change the collection setting.

## SKAdNetwork and AdAttributionKit

On iOS, the SDK (0.7.0+) sets Apple's conversion value for you, so SKAdNetwork and AdAttributionKit postbacks are sent even for users who don't allow tracking:

-   On first launch it registers the install (value 0, coarse `low`).
-   It raises the value from AppRefer's own data: **10** (`medium`) once a free trial starts, **20 + whole dollars paid** (`high`, capped at 63) once the user pays. Values only go up.
-   It checks at launch, when the app becomes active, after trial and revenue events and after AppRefer Billing purchases, for 35 days. It updates AdAttributionKit (iOS 17.4+) and SKAdNetwork (iOS 15.4+).

If you take payments outside AppRefer Billing, call this after a purchase. On Android it does nothing.

```typescript
await AppRefer.updateConversionValue();
```

To get copies of the winning postbacks in AppRefer, add these keys to your iOS `Info.plist`, plus the `SKAdNetworkItems` identifiers your ad networks publish. See [SKAdNetwork](https://apprefer.com/docs/integrations/skan.md).

**ios/YourApp/Info.plist**

```xml
<key>NSAdvertisingAttributionReportEndpoint</key>
<string>https://apprefer.com</string>
<key>AttributionCopyEndpoint</key>
<string>https://apprefer.com</string>
```

## AppRefer Billing

`AppReferBilling` runs App Store and Google Play subscriptions and one-time purchases through `react-native-iap` (StoreKit 2 on iOS, Play Billing on Android). AppRefer verifies each purchase with Apple or Google, acknowledges Google purchases, keeps the customer's entitlements and sends the conversion to your ad networks from the same attribution. It lives in its own entry point, `@apprefer/react-native-sdk/billing`, so attribution-only apps don't need `react-native-iap`. Billing must be turned on for the app in AppRefer first; see [Billing](https://apprefer.com/docs/billing.md) for the server setup and the move from RevenueCat.

### Set up

```bash
npm install react-native-iap react-native-nitro-modules
cd ios && pod install
```

```typescript
import { AppRefer } from '@apprefer/react-native-sdk';
import { AppReferBilling } from '@apprefer/react-native-sdk/billing';

AppRefer.configure({ apiKey: 'pk_live_...' });

// Same SDK key. Starts the store purchase listener right away.
await AppReferBilling.configure({
  appUserId: null, // null = anonymous "$ARAnon:<UUID>"
  entitlementsByProduct: {
    pro_monthly: ['premium'],
    pro_yearly: ['premium'],
  },
  consumableProductIds: ['coins_100'], // Google Play consumables; iOS reads the type from StoreKit
});
```

| Option | Default | Description |
| --- | --- | --- |
| `apiKey` | the configure key | SDK key, if different |
| `appUserId` | null | Your user id; `null` starts anonymous |
| `entitlementsByProduct` | {} | Product id to entitlement ids, used only when AppRefer can't be reached |
| `defaultEntitlement` | undefined | Entitlement for unmapped products (the server default is `premium`) |
| `consumableProductIds` | \[\] | Google Play one-time products consumed after AppRefer records them |

> **Let AppRefer complete purchases**
>
> Don't call `finishTransaction`, `acknowledgePurchaseAndroid` or `consumePurchaseAndroid` yourself, and don't handle purchases in your own `purchaseUpdatedListener`. AppRefer Billing completes each purchase once AppRefer has recorded it. Listening for UI is fine. `react-native-iap` 17+ is a native module, so Expo apps need a development build.

### Offerings and paywalls

Offerings let you change what a paywall sells from the dashboard (packages, placements, targeting and experiments) without hard-coding product ids. See [Offerings](https://apprefer.com/docs/offerings.md).

```typescript
const offerings = await AppReferBilling.offerings();

const annual = offerings.currentOffering('onboarding')?.annual;
if (annual) {
  setPrice(annual.storeProduct.displayPrice);
  const result = await AppReferBilling.purchasePackage(annual);
}
```

-   `offerings.current` is the resolved default and `offerings.all` holds every offering by id. Each offering has `availablePackages`, `metadata`, a package lookup and the shortcuts `weekly`, `monthly`, `twoMonth`, `threeMonth`, `sixMonth`, `annual` and `lifetime`.
-   An unknown placement falls back to the current offering; a placement set to no offering returns `null` (show no paywall).
-   Buying through `purchasePackage` records the offering, placement and experiment with the purchase.
-   Offerings are cached for five minutes (memory and AsyncStorage). Offline, the last copy is used; with no copy at all the call rejects, so keep your existing product ids as a fallback.

### Products, purchase and restore

```typescript
const products = await AppReferBilling.products(['pro_monthly', 'pro_yearly']); // react-native-iap products, in this order

const result = await AppReferBilling.purchase(products[1]);
if (result.userCancelled) return;
if (result.outcome === 'pending') showPendingNote(); // Ask to Buy / slow payment: arrives later
if (result.customerInfo.entitlements.active.premium) unlock();

const info = await AppReferBilling.restorePurchases(); // AppStore.sync() on iOS, then sends every owned purchase
```

-   `purchase` tags the purchase with the customer's AppRefer account token (Apple `appAccountToken`, Google `obfuscatedAccountId`), sends it to AppRefer and only then completes it.
-   Google Play options: `offerToken` (default: a free-trial offer the user is eligible for, else the base plan; see `defaultOfferToken(product)`), and `oldProductId` with `replacementMode` (default `with-time-proration`) for upgrades and downgrades.
-   App Store options: `promotionalOffer` and `winBackOfferId`.
-   Who is buying is saved before the sheet opens, so a purchase that completes later still goes to them. If that can't be saved, `purchase` rejects with a `storage` error and the sheet doesn't open.
-   Only `restorePurchases()` can move a purchase owned by another app user to the one signed in. `AppReferBilling.rejectedPurchases()` lists purchases AppRefer refused for good, for your support team.

### Customer info and entitlements

```typescript
const info = await AppReferBilling.customerInfo(); // cached for 5 minutes, then fetched
const fresh = await AppReferBilling.customerInfo({ fetchPolicy: 'fetchCurrent' });

if (info.entitlements.active.premium) { /* unlocked */ }
info.entitlements.all.premium?.expirationDate; // ISO 8601, null for lifetime

const remove = AppReferBilling.addCustomerInfoUpdateListener(update);
remove(); // or AppReferBilling.removeCustomerInfoUpdateListener(update)
```

| Field | Description |
| --- | --- |
| `entitlements` | `.all` and `.active`, by id |
| `activeSubscriptions` | Product ids of active subscriptions |
| `appUserId` | The customer the answer is for |
| `source` | `'server'`, `'cache'` or `'localFallback'` |
| `EntitlementInfo` | `isActive`, `productIdentifier`, `expirationDate` (ISO string) and `expirationDateMillis`, `willRenew`, `periodType` (`normal`, `trial`, `intro`, `promotional`) |

`customerInfo` never rejects, and the objects are plain JSON, safe to keep in React state. If AppRefer can't be reached, entitlements come from the verified store purchases on the device, mapped through `entitlementsByProduct`, so a paying user is never locked out.

### Users

```typescript
await AppReferBilling.logIn('user_123'); // moves purchases made while anonymous to this user
await AppReferBilling.logOut();          // new anonymous id

const id = await AppReferBilling.appUserId(); // "$ARAnon:..." while anonymous
const anonymous = await AppReferBilling.isAnonymous();

await AppReferBilling.setEmail('a@example.com'); // also setDisplayName, setPhoneNumber, setAttributes
```

The install's device id is sent with purchases automatically, so purchases are matched to the ad click without `setUserId`. It is left out while collection is off and after `reset()` until `configure` runs again. The anonymous id is kept in AsyncStorage, so a reinstall starts a new one; a restore still finds the purchases.

### Sandbox and App Review

-   Test with a sandbox Apple ID or a Play license tester. Those purchases are recorded as sandbox, unlock the entitlement and are never forwarded to ad networks or counted as revenue.
-   App Review's sandbox purchases in your release build work with the live key.
-   A real (production) purchase sent with a `pk_test_` key is refused.
-   Transactions from an Xcode StoreKit configuration file are signed with a local certificate AppRefer rejects; the SDK still unlocks them locally, but they never reach AppRefer.

## Support

An in-app help chat your team answers from the AppRefer inbox, with screenshots, quick-help topics, reply notifications and a help centre. Built from React Native core components, no extra runtime packages, 35 languages. Server setup and the inbox are covered in [Support](https://apprefer.com/docs/support.md).

### Open the chat

Mount the modal host once, next to your navigation:

```tsx
import { AppRefer, AppReferSupportHost, AppReferSupportView } from '@apprefer/react-native-sdk';

function Root() {
  return (
    <>
      <AppNavigation />
      <AppReferSupportHost />
    </>
  );
}

AppRefer.support.open();                                  // help home
AppRefer.support.open('cv_...');                          // one conversation
AppRefer.support.compose('Problem with my subscription'); // home with the message field filled in

// Or embed it in your own screen (use either the host or the view, not both)
<AppReferSupportView opening={{ kind: 'helpCentre' }} onClose={goBack} />
// { kind: 'home' }, { kind: 'article', slug }, { kind: 'conversation', id }
```

### Identify and customise

```typescript
import { supportTopics } from '@apprefer/react-native-sdk';

await AppRefer.support.identify('Alex', 'alex@example.com'); // user id is sent automatically
AppRefer.support.accentColor = '#7954ff';   // hex or rgb(); the palette is derived from it
AppRefer.support.title = 'Sparkly support'; // default "<app name> support", translated
AppRefer.support.teamNames = ['Sam'];       // first names on the home
AppRefer.support.replyTime = 'a few hours'; // completes "We usually reply in …"
AppRefer.support.appIcon = require('./assets/icon.png');
AppRefer.support.topics = [
  supportTopics.billing,
  Object.assign(supportTopics.restore, { action: () => AppReferBilling.restorePurchases() }),
  supportTopics.bug,
  { title: 'Workouts', subtitle: 'Logging and plans', icon: 'chat', tint: '#FF9500', prompt: 'Which workout?' },
];
AppRefer.support.language = 'de'; // force a language; undefined follows the app
AppRefer.support.strings = { 'support.home.subtitle': 'What can we fix for you?' };
```

Quick-help tiles default to `billing`, `restore`, `bug` and `idea`; an empty list hides them. A tile with an `action` runs it instead of starting a message. `icon` is a built-in name (`billing`, `restore`, `bug`, `idea`, `chat`, `chats`, `camera`…) or a render function. The chat follows the app's language, then the device, then English.

### Reply notifications

AppRefer sends reply notifications itself: through APNs with your key on iOS and FCM with your service account on Android. It emails a reply only if it stays unread for an hour. Your app owns the notification permission and messaging setup; the SDK adds no messaging dependency. Register the token once `status().pushReady` is true: the APNs device token (hex) on iOS, the FCM token on Android.

```typescript
if ((await AppRefer.support.status()).pushReady) {
  await AppRefer.support.registerPushToken(token);
}

// On a notification tap (not on background receipt)
if (AppRefer.support.handleNotification(remoteMessage.data ?? {})) return;

// Before unregistering a device token
await AppRefer.support.removePushToken(oldToken);

const unread = await AppRefer.support.unreadCount(); // for a badge
```

On Android, replies arrive on the `apprefer_support` channel. Call `registerPushToken` again when the token rotates. **Push environment (0.10.1+).** On iOS the token is registered for the environment the build is signed for, read from the embedded profile's `aps-environment`: development-signed builds (Debug or Release) and the Simulator use sandbox; App Store, TestFlight, Ad Hoc and Enterprise builds use production. Force one with `AppRefer.support.pushEnvironment = 'sandbox'` or `registerPushToken(token, 'sandbox')`. Android always uses production.

### Help centre

Published help articles appear in the chat with no code (0.10.0+): a "Help articles" card on the home, a searchable help centre and a reader. You can open them directly or build your own screens.

```typescript
import { SupportError } from '@apprefer/react-native-sdk';

AppRefer.support.openHelpCentre();             // with <AppReferSupportHost /> mounted
AppRefer.support.openArticle('restore-purchases');

const articles = await AppRefer.support.articles();         // AppReferHelpArticle[]
const results = await AppRefer.support.articles('refund');  // server search, up to 20
try {
  const article = await AppRefer.support.article('refunds'); // + body (markdown)
} catch (e) {
  if (e instanceof SupportError && e.status === 404) { /* removed or unpublished */ }
}
```

Articles come in the chat's language and are cached for five minutes; offline, the last copy is shown. Test keys also see drafts.

### Status

```typescript
const status = await AppRefer.support.status();
status.pushReady;          // this platform's push verified (on iOS, for this build's environment)
status.emailReplies;       // customers can answer reply emails
status.helpCentreArticles; // number of published articles
```

`status()` follows the server's caching (five minutes once every channel is verified, otherwise 30 seconds), so calling it at every launch is cheap.

### Support without attribution

Apps that want only the chat can start Support on its own. It doesn't configure attribution, collect device signals or send analytics.

```typescript
AppRefer.support.configure('pk_live_...');

// Forget the name, email and user id and start a fresh anonymous session
await AppRefer.support.clearIdentity();
```

## Requirements

| Requirement | Minimum |
| --- | --- |
| React Native | 0.70 (React 18) |
| iOS | 15.0 |
| Android | API 23 |
| Peer dependencies | @react-native-async-storage/async-storage 1.17+; for Billing, react-native-iap 17+ with react-native-nitro-modules |

## Changelog highlights

| Version | What changed |
| --- | --- |
| 0.13.1 | Narrow-screen Support text wrapping, measured composer spacing and keyboard anchoring. React Native Support-only apps bundle without the optional purchase package. |
| 0.13.0 | Photo/video testimonials from Support, the app or an authenticated coach; usage terms accepted on Submit, signed uploads and receipt-based rewards. See the Support reference for integration. |
| 0.12.0 | Verified customer context, resolution actions, offering and optional review cards, outcomes and tailored Support setup. |
| 0.11.0 | Version parity release. Nothing on this page changes; run `pod install` after upgrading. |
| 0.10.1 | iOS push tokens use the build's signing environment (new native module); new `support.pushEnvironment` and the `environment` argument on `registerPushToken`. |
| 0.10.0 | Help centre in the chat; `support.articles`, `article`, `status()`, `openHelpCentre()`, `openArticle()`. Chat in 35 languages with `language` and `strings` overrides. |
| 0.9.3 | The Billing account gate set by `reset()` survives a relaunch. |
| 0.9.2 | Billing sends no install device id while collection is off or after `reset()`; new `support.clearIdentity()`. |
| 0.9.1 | Events carry `app_user_id`; `setCollectionEnabled`; after `reset()` call `configure` again. |
| 0.9.0 | `reset()`, Support-only configure, durable event queue, `trackEventResult`, `clearPendingEvents`, `configureResult`. |
| 0.8.0 | Offerings, placements and experiments; `purchasePackage`. Products mapped to no entitlement unlock nothing offline. |
| 0.7.0 | SKAdNetwork and AdAttributionKit conversion values on iOS; `updateConversionValue()`. |
| 0.6.0 | Tracking consent split: `trackingConsentDidChange()` (iOS) and `setTrackingConsent` (Android); requests pick their host from consent. |

Full history: the `CHANGELOG.md` in [apprefer-react-native-sdk](https://github.com/AppAgentic/apprefer-react-native-sdk).

---

Source: https://apprefer.com/docs/sdk/react-native · Last updated: 2026-10-20
