> For the complete documentation index, see [llms.txt](https://docs.tiun.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.tiun.io/guides/app-integration/monetize-in-react-native.md).

# Integrate with React Native

This guide walks through a complete React Native integration: creating products, installing the SDK, wiring checkout, gating content on what the user has bought, and going live.

The tiun React Native SDK covers **authentication and checkout** — subscriptions and one-time purchases. It renders the tiun overlays inside your app and binds the user's session to a hardware-backed device key, so the session survives restarts without you storing anything.

***

## 1. Set up your environment and products

Most teams build against sandbox first: click the **Sandbox** toggle in the dashboard sidebar and **Enable** it — see [setting up your environment](/guides/getting-started/set-up-environment.md).

Then, in the environment you'll point the app at, go to **Settings → Environment setup**, turn on **App**, and enter your **app scheme** — for example `myapp://`. This is how checkout gets back into your app after payment; you'll register the same scheme natively in step 3. tiun checks your app's `returnUrl` against this scheme when login or checkout opens, so a scheme that isn't registered here — or is registered in the other environment — fails right away, not at the end of payment.

Create one product per plan — for example **Light** and **Pro** — and copy their product IDs. The full walkthrough is in [creating your first product](/guides/getting-started/creating-first-product.md). Copy your **snippet ID** from the same environment.

***

## 2. Install the SDK

Install the SDK and its native dependencies, then rebuild:

```bash
npm install @tiun/react-native-sdk react-native-webview \
  react-native-secure-sign react-native-keychain \
  react-native-inappbrowser-nitro react-native-nitro-modules
```

```bash
cd ios && bundle exec pod install
```

These are native modules, so a JavaScript reload isn't enough — rebuild and reinstall the app. The full list of what each dependency does is in [Installation](/sdk/react-native-sdk/installation.md).

***

## 3. Register the return deep link

After payment, the provider redirects back into your app through a deep link that you choose — for example `myapp://tiun/return`. Register its scheme natively, or checkout can never complete.

{% hint style="info" %}
**One scheme, three places.** The scheme has to match everywhere: the **App scheme** in the dashboard's Environment setup (`myapp://`), the native registration below (`myapp`), and the `returnUrl` you pass to the SDK in step 4 (`myapp://tiun/return`). The host and path after the scheme are up to you.
{% endhint %}

On iOS, add it to `Info.plist`:

```xml
<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleURLName</key>
    <string>tiun.checkout.return</string>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>myapp</string>
    </array>
  </dict>
</array>
```

Still on iOS, forward incoming links to React Native's `Linking` module in `AppDelegate.swift` — the `Info.plist` entry alone doesn't get the link to React Native, and checkout won't resolve without it:

```swift
func application(
  _ application: UIApplication,
  open url: URL,
  options: [UIApplication.OpenURLOptionsKey: Any] = [:]
) -> Bool {
  return RCTLinkingManager.application(application, open: url, options: options)
}
```

On Android, add an intent filter to your main activity and set `android:launchMode="singleTask"`:

```xml
<intent-filter>
  <action android:name="android.intent.action.VIEW" />
  <category android:name="android.intent.category.DEFAULT" />
  <category android:name="android.intent.category.BROWSABLE" />
  <data android:scheme="myapp" android:host="tiun" android:pathPrefix="/return" />
</intent-filter>
```

***

## 4. Add the provider

Keep your config and product IDs in one module so every screen references the same values:

```tsx
// tiun.ts
import type { TiunConfig } from '@tiun/react-native-sdk';

export const TIUN_CONFIG: TiunConfig = {
  snippetId: 'YOUR_SANDBOX_SNIPPET_ID',
  host: 'https://api-sandbox.tiun.live',
  returnUrl: 'myapp://tiun/return',
  language: 'en',
  debug: true,
};

export const TIUN_PRODUCTS = {
  light: 'p-test-light',
  pro: 'p-test-pro',
} as const;
```

Wrap your app root in `<TiunProvider>`. The one rule is that it sits above everything that uses the tiun hooks — the overlay sizes itself to the window, so it covers your UI from anywhere in the tree:

```tsx
import { TiunProvider } from '@tiun/react-native-sdk';
import { TIUN_CONFIG } from './tiun';

export default function App() {
  return (
    <TiunProvider config={TIUN_CONFIG}>
      <RootNavigator />
    </TiunProvider>
  );
}
```

The provider takes up no space on screen until the user opens login or checkout.

***

## 5. Build a pricing screen

One button per plan, each calling `checkout()` with its product ID:

```tsx
import { Button, View } from 'react-native';
import { useTiun } from '@tiun/react-native-sdk';
import { TIUN_PRODUCTS } from './tiun';

function PricingScreen() {
  const { checkout } = useTiun();

  return (
    <View>
      <Button
        title="Light — EUR 19/mo"
        onPress={() => checkout({ productId: TIUN_PRODUCTS.light })}
      />
      <Button
        title="Pro — EUR 199/mo"
        onPress={() => checkout({ productId: TIUN_PRODUCTS.pro })}
      />
    </View>
  );
}
```

tiun opens the checkout overlay — it collects the user's email, verifies their identity, takes payment, and grants access in one flow. Payment itself happens in the in-app browser and returns to your app through the deep link you registered in step 3. For what the overlay does behind the scenes, see [Checkout / How it works](/reference/checkout/how-it-works.md) in Reference.

***

## 6. Gate content on what the user bought

After a successful purchase the user's `productAccess` array contains the product they just bought. It's the canonical source of truth for what they can access — see [Product access](/reference/checkout/product-access.md) in Reference.

Derive the current tier in one place:

```tsx
import { useTiun } from '@tiun/react-native-sdk';
import { TIUN_PRODUCTS } from './tiun';

type Tier = 'free' | 'light' | 'pro';

export function useTier(): Tier {
  const { user } = useTiun();
  const access = user?.productAccess ?? [];

  if (access.includes(TIUN_PRODUCTS.pro)) return 'pro';
  if (access.includes(TIUN_PRODUCTS.light)) return 'light';
  return 'free';
}
```

Then gate your screens on it:

```tsx
import { Button } from 'react-native';

function PremiumScreen() {
  const tier = useTier();
  const { checkout } = useTiun();

  if (tier !== 'pro') {
    return (
      <Button
        title="Upgrade to Pro"
        onPress={() => checkout({ productId: TIUN_PRODUCTS.pro })}
      />
    );
  }

  return <ProContent />;
}
```

`useTiun()` re-renders your component whenever the user changes, so the same code covers checkout success, login, logout, and the session restore at launch. If you need to *react* to a change rather than render from it — refetching data, resetting a screen — subscribe with `useTiunEvent('userChange', ...)`.

{% hint style="info" %}
**The `userChange` payload has no `event` field in React Native.** If you're porting from the web SDK, replace `data.event === 'login'` checks with the separate `login` and `logout` events. See [Hooks and events](/sdk/react-native-sdk/hooks.md).
{% endhint %}

***

## 7. Add login and logout

Returning subscribers sign in without buying again — their existing `productAccess` comes back with the session, and your gating picks it up unchanged.

```tsx
import { Button } from 'react-native';
import { useTiun } from '@tiun/react-native-sdk';

function AuthButton() {
  const { isAuthenticated, login, logout } = useTiun();

  return (
    <Button
      title={isAuthenticated ? 'Log out' : 'Log in'}
      onPress={() => (isAuthenticated ? logout() : login())}
    />
  );
}
```

***

## 8. Verify on your server

Gating in the app controls what it renders. If your backend serves the premium payload itself, verify the user there before responding — a client can be modified.

Get a signed token from the SDK and send it with the request:

```tsx
const { getUserVerificationToken } = useTiun();

async function fetchPremium() {
  const token = await getUserVerificationToken();
  if (!token) return null;

  const response = await fetch('https://api.example.com/premium', {
    headers: { Authorization: `Bearer ${token}` },
  });

  return response.ok ? response.json() : null;
}
```

The token is the same signed JWT the web SDK issues, so the backend half is unchanged — see [verify authentication server-side](/guides/authentication/verify-server-side.md) for the exchange, and [verify subscriptions server-side](/guides/subscriptions/verify-server-side.md) for checking entitlements.

***

## 9. Test on a device

The user's session is bound to a key in the device's secure hardware, which limits where you can test:

* **iOS Simulator can't do login or checkout** — there's no Secure Enclave. Use a physical iPhone.
* **Android emulators and devices work** normally.
* On iOS, checkout shows a one-time system prompt — *"…wants to use…to sign in"*. That's expected; it's how the payment redirect gets back into your app.
* Set `debug: true` in your config to see `[tiun]` bridge logs in the Xcode or Android console.
* **Check that Metro can reach your iPhone.** Office and guest Wi-Fi often isolate clients, and the failure is silent — the app keeps running its last cached bundle, so your changes look like they did nothing. Personal Hotspot is the quickest way to rule it out.

Use [simulated payments](/guides/testing/simulate-payments.md) in sandbox so you can run the full purchase flow without real cards.

### If login or checkout doesn't work

Most failures come from the return scheme not matching across the three places from step 3. What you see tells you which one:

| What you see                                          | Likely cause                                                                                                                                                | Fix                                                                                                                                                                     |
| ----------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Login or checkout opens and closes straight away      | The scheme in your `returnUrl` isn't the **App scheme** in the dashboard — or it's registered only in a different environment from the one `host` points at | Compare `returnUrl` with **Settings → Environment setup → App scheme**, character for character, in the environment you target                                          |
| Payment succeeds, but the app never finishes checkout | The scheme is accepted by tiun, but the app doesn't receive the deep link                                                                                   | Check the scheme in `Info.plist` and the Android intent filter, the `RCTLinkingManager` forwarding in `AppDelegate`, and `android:launchMode="singleTask"` — see step 3 |
| Login or checkout fails only on the iOS Simulator     | No Secure Enclave                                                                                                                                           | Test on a physical iPhone                                                                                                                                               |

After changing anything native — `Info.plist`, `AppDelegate`, or `AndroidManifest.xml` — rebuild and reinstall the app; a JavaScript reload doesn't pick it up.

***

## 10. Go live

When the sandbox integration works:

1. Set up **live** in the dashboard, if you haven't — register your app scheme in the live **Settings → Environment setup**, and create live products and API keys; all are separate from sandbox.
2. Remove `host` from your config (or set it to `https://api.tiun.live`) so the app targets live.
3. Swap in your **live** snippet ID and live product IDs (`p-test-…` → `p-live-…`).
4. Turn off `debug`.
5. If you verify server-side, switch to the live API base URL and a live API key.
6. [Enable payouts](/guides/getting-started/enable-payouts.md) so live revenue can be paid out to you.

```tsx
export const TIUN_CONFIG: TiunConfig = {
  snippetId: 'YOUR_LIVE_SNIPPET_ID',
  returnUrl: 'myapp://tiun/return',
  language: 'en',
};
```

The SDK API is identical across environments — only these IDs and credentials change. See [setting up your environment](/guides/getting-started/set-up-environment.md) for the full sandbox-to-live workflow.

***

For the full hook and event reference, see [Hooks and events](/sdk/react-native-sdk/hooks.md) in the SDK docs, and [React Native examples](/sdk/react-native-sdk/examples.md) for more patterns.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.tiun.io/guides/app-integration/monetize-in-react-native.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
