# React Native

> Identify devices in React Native apps through the native iOS and Android SDKs, with one TypeScript API and the same verdicts on both platforms.

Last updated: 2026-09-17

`@fingerly/react-native` is a thin bridge to the native [iOS](https://docs.fingerly.io/docs/sdks/ios) and [Android](https://docs.fingerly.io/docs/sdks/android) SDKs. Almost every signal worth having on a phone is out of reach of JavaScript, so the bridge calls native code rather than collecting less.

## Requirements

- React Native 0.74 or newer, including the New Architecture.
- iOS 13 and Android 5.0 (API 21) or newer.
- A development build. Expo Go cannot load native modules.

## Install

```bash npm
npm install @fingerly/react-native
cd ios && pod install
```

```bash pnpm
pnpm add @fingerly/react-native
cd ios && pod install
```

```bash yarn
yarn add @fingerly/react-native
cd ios && pod install
```

Autolinking adds the native modules. Rebuild the app after installing.

## Identify a device

```ts fingerly.ts
import { load } from '@fingerly/react-native'

const fingerly = await load({ apiKey: 'fly_pk_us_production_…' })

export async function signIn(email: string, password: string) {
  const { requestId } = await fingerly.identify({ tag: 'sign-in' })
  return api.signIn({ email, password, requestId })
}
```

Send `requestId` to your backend with the action, and decide there after reading the stored event with a secret key. See [server-side verification](https://docs.fingerly.io/docs/server-side-verification).

## API

**`load(options)`**

- `apiKey` (string, required): Your public key.
- `endpoint` (string): A custom API origin or first-party proxy.
- `consent` ('granted' | 'pending' | 'denied', default `'granted'`): Until it is `granted`, `identify` and `collect` read and send nothing and throw `ConsentError`. The state is shared by the whole app, so pass it on every `load`. See [consent](https://docs.fingerly.io/docs/privacy-and-consent#consent).

**`identify(options)`**

- `tag` (string): Your own reference for this identification.
- `submit` (boolean, default `true`): Set `false` to collect without sending.

`identify` resolves with the same fields as the native SDKs: `requestId`, `visitorId`, `visitorIsNew`, `visitorConfidence`, `identifiable`, `duplicate`, `state`, `suspectScore` (`number | null`), `suspectLevel`, `triggers`, `verdicts` and `report`. `collect()` returns a report without submitting. `setConsent(state)` changes the consent state, and `consent` reads it.

## Verdicts

| Verdict | Platform | What it means |
| --- | --- | --- |
| `instrumentation` | Both | An instrumentation toolkit is attached to the app. |
| `mitm` | Both | Something is intercepting the app's encrypted traffic. |
| `automation` | Both | A debugger or a test runner is driving the app. |
| `tampering` | Both | The app's code has been hooked or modified. |
| `farm` | Both | The device looks mass-provisioned or freshly reset. |
| `jailbreak`, `simulator` | iOS | Jailbroken device; iOS Simulator. |
| `root`, `emulator`, `appCloner` | Android | Rooted device; emulator; cloning framework. |

Verdicts a platform cannot answer are returned as `{ value: false, confidence: 'low', reasons: [] }`, so the shape is the same on both.

## Errors

```ts errors.ts
import { FingerlyError } from '@fingerly/react-native'

try {
  await fingerly.identify()
} catch (error) {
  if (error instanceof FingerlyError && error.status === 401) {
    // the key is wrong or revoked
  }
}
```

Retries happen in the native layer. If the native module is missing, every call throws a `FingerlyError` that explains how to rebuild. Without consent, calls throw `ConsentError`, a `FingerlyError` whose `code` is `consent_required`.
