> ## Documentation Index
> Fetch the complete documentation index at: https://docs.didit.me/llms.txt
> Use this file to discover all available pages before exploring further.

# React Native SDK

> React Native and Expo SDK for KYC, KYB, biometric liveness, and AML. TypeScript API, native iOS and Android, NFC. Pay-per-call from $0.30, 500 free/month.

export const AgentPromptAccordion = ({prompt, title = "AI Agent Integration Prompt"}) => {
  const [copied, setCopied] = React.useState(false);
  const handleCopy = e => {
    e.stopPropagation();
    if (!prompt) return;
    navigator.clipboard.writeText(prompt.trim()).then(() => {
      setCopied(true);
      setTimeout(() => setCopied(false), 2000);
    });
  };
  const agents = ["Claude Code", "Codex", "Cursor", "Devin", "Windsurf", "GitHub Copilot"];
  return <div className="didit-agent-card">
      {}
      <div className="didit-agent-titlebar">
        <div className="didit-agent-dots" aria-hidden="true">
          <span className="didit-agent-dot didit-agent-dot-red"></span>
          <span className="didit-agent-dot didit-agent-dot-yellow"></span>
          <span className="didit-agent-dot didit-agent-dot-green"></span>
        </div>
        <span className="didit-agent-filename">{title}</span>
        <button type="button" className={`didit-agent-copy ${copied ? "didit-agent-copy-copied" : ""}`} onClick={handleCopy} title="Copy prompt to clipboard" aria-label={copied ? "Copied!" : "Copy prompt to clipboard"}>
          {copied ? <>
              <svg width="13" height="13" viewBox="0 0 16 16" fill="none">
                <path d="M3 8.5l3.5 3.5L13 4" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
              </svg>
              <span>Copied</span>
            </> : <>
              <svg width="13" height="13" viewBox="0 0 16 16" fill="none">
                <rect x="5" y="5" width="9" height="9" rx="1.5" stroke="currentColor" strokeWidth="1.5" />
                <path d="M11 5V3.5A1.5 1.5 0 0 0 9.5 2h-6A1.5 1.5 0 0 0 2 3.5v6A1.5 1.5 0 0 0 3.5 11H5" stroke="currentColor" strokeWidth="1.5" />
              </svg>
              <span>Copy</span>
            </>}
        </button>
      </div>

      {}
      <pre className="didit-agent-body"><code>{prompt.trim()}</code></pre>

      {}
      <div className="didit-agent-footer">
        <span className="didit-agent-footer-label">Paste into</span>
        <div className="didit-agent-chips">
          {agents.map(name => <span key={name} className="didit-agent-chip">{name}</span>)}
        </div>
      </div>
    </div>;
};

<AgentPromptAccordion
  title="React Native SDK Integration Prompt"
  prompt={`Integrate Didit identity verification into my React Native app using the native SDK.

## Requirements
- React Native 0.76+ (New Architecture / TurboModules)
- Node.js 20+, TypeScript 5+
- iOS 13.0+ (NFC requires iOS 15+)
- Android API 24+ (Android 7.0), Kotlin 1.9+, Java 17+
- New Architecture (TurboModules) only — package is published as @didit-protocol/sdk-react-native v3.x

## Installation

### Expo (recommended)
npx expo install @didit-protocol/sdk-react-native

Add to app.json (NFC is enabled by default):
{
"expo": {
"plugins": ["@didit-protocol/sdk-react-native"]
}
}

To opt out of NFC dependencies:
{
"expo": {
"plugins": [["@didit-protocol/sdk-react-native", { "iosNfcEnabled": false, "androidNfcEnabled": false }]]
}
}

The config plugin automatically adds the Didit Maven repo (Android), the DiditSDK podspec (iOS),
and BouncyCastle packaging rules for Android. This SDK requires a development build — it does not work in Expo Go.

### React Native CLI
npm install @didit-protocol/sdk-react-native

iOS — add this block to ios/Podfile inside the app target (NFC-aware; pins to iOS 15 when NFC is on):
didit_sdk_ios_nfc_enabled = ENV.fetch('DIDIT_SDK_IOS_NFC_ENABLED', 'true').downcase != 'false'
didit_sdk_ios_pod = didit_sdk_ios_nfc_enabled ? 'DiditSDK' : 'DiditSDK/Core'
pod didit_sdk_ios_pod, :podspec => 'https://raw.githubusercontent.com/didit-protocol/sdk-ios/main/DiditSDK.podspec'
Then: cd ios && bundle exec pod install

Android — add to settings.gradle (dependencyResolutionManagement.repositories):
maven { url "https://raw.githubusercontent.com/didit-protocol/sdk-android/main/repository" }
To disable NFC, add diditSdkAndroidNfcEnabled=false to android/gradle.properties.

## Backend: Create Session (your server)
POST https://verification.didit.me/v3/session/
Headers: { "x-api-key": DIDIT_API_KEY, "Content-Type": "application/json" }
Body: { "workflow_id": DIDIT_WORKFLOW_ID, "vendor_data": "user-id" }
Response: { "session_id": "uuid", "session_token": "short-token", "url": "https://verify.didit.me/session/short-token", "status": "Not Started" }
Send session_token to the React Native app (never expose DIDIT_API_KEY to the device).

## Integration
import { startVerification, VerificationStatus } from '@didit-protocol/sdk-react-native';

// Option A: With session token from your backend (recommended)
const result = await startVerification('session-token-from-backend');

// Option B: With workflow ID (SDK creates session internally)
const result = await startVerificationWithWorkflow('your-workflow-id', {
vendorData: 'user-123',
});

// Handle result
switch (result.type) {
case 'completed':
if (result.session.status === VerificationStatus.Approved) { /* grant access */ }
break;
case 'cancelled':
// User dismissed the verification flow
break;
case 'failed':
// SDK error — show result.error.message
break;
}

## Result Statuses (VerificationStatus enum)
- VerificationStatus.Approved — user identity verified, grant access
- VerificationStatus.Declined — verification failed, show retry or contact support
- VerificationStatus.Pending  — needs manual review by your compliance team

## Full Verification Results
The SDK only returns the status. Full decision data (document fields, face match scores, AML hits, etc.) arrives via webhook to your backend.
Set up webhooks: https://docs.didit.me/integration/webhooks

## Rate Limits (session creation)
- 600 session-create requests / minute per x-api-key (see https://docs.didit.me/integration/rate-limiting)
- On 429, retry honouring Retry-After / X-RateLimit-* headers

## Environment Variables (backend only — never expose in the app)
- DIDIT_API_KEY — from Didit Console > API & Webhooks
- DIDIT_WORKFLOW_ID — from Didit Console > Workflows

## Docs
- React Native SDK: https://docs.didit.me/integration/native-sdks/react-native-sdk
- GitHub: https://github.com/didit-protocol/sdk-react-native
- npm: https://www.npmjs.com/package/@didit-protocol/sdk-react-native
- Webhooks: https://docs.didit.me/integration/webhooks
- API Reference: https://docs.didit.me/api-reference/overview`}
/>

A cross-platform React Native SDK that wraps the native iOS and Android SDKs, providing a unified TypeScript API for identity verification.

<Card title="GitHub Repository" icon="github" href="https://github.com/didit-protocol/sdk-react-native">
  View source code and examples on GitHub
</Card>

<Card title="npm Package" icon="npm" href="https://www.npmjs.com/package/@didit-protocol/sdk-react-native">
  @didit-protocol/sdk-react-native
</Card>

<Note>
  ### **Native SDK**: This is the recommended approach for React Native and Expo apps. The SDK wraps the native iOS and Android SDKs for the best user experience, optimized camera handling, and full NFC support.
</Note>

***

## Requirements

| Requirement  | Minimum Version                         |
| ------------ | --------------------------------------- |
| React Native | 0.76+ (New Architecture / TurboModules) |
| Node.js      | 20+                                     |
| TypeScript   | 5+                                      |

### Platform Requirements

| Platform | Minimum Version | Notes                                   |
| -------- | --------------- | --------------------------------------- |
| iOS      | 13.0+           | NFC passport reading requires iOS 15.0+ |
| Android  | API 24+ (7.0)   | Kotlin 1.9+, Java 17+                   |

***

## Installation

### Expo (Recommended)

```bash theme={null}
npx expo install @didit-protocol/sdk-react-native
```

Then add the config plugin to your `app.json` (or `app.config.js`). NFC is enabled by default:

```json theme={null}
{
  "expo": {
    "plugins": ["@didit-protocol/sdk-react-native"]
  }
}
```

That's it. The plugin automatically configures both platforms:

* **Android:** Adds the Didit Maven repository to Gradle, sets the `diditSdkAndroidNfcEnabled` Gradle property, and applies BouncyCastle dependency/packaging rules
* **iOS:** Adds the DiditSDK podspec to the Podfile (pinned to iOS 15 when NFC is enabled)

#### Disabling NFC dependencies

To build without NFC support (smaller binary, no NFC capability required), pass plugin options:

```json theme={null}
{
  "expo": {
    "plugins": [
      [
        "@didit-protocol/sdk-react-native",
        { "iosNfcEnabled": false, "androidNfcEnabled": false }
      ]
    ]
  }
}
```

When NFC is disabled, you do not need the iOS NFC capability, NFC entitlements, or NFC-related provisioning setup.

<Warning>
  This SDK uses native modules (camera, NFC) that are **not available in Expo Go**. You must use a [development build](https://docs.expo.dev/develop/development-builds/introduction/) or run `npx expo prebuild` to generate the native projects.
</Warning>

### React Native CLI

```bash theme={null}
npm install @didit-protocol/sdk-react-native
# or
yarn add @didit-protocol/sdk-react-native
```

#### iOS Setup

Add the DiditSDK pod to your `Podfile` (it's not on CocoaPods trunk). The block below honours an `DIDIT_SDK_IOS_NFC_ENABLED` environment variable so you can toggle the no-NFC subspec (`DiditSDK/Core`) at install time:

```ruby theme={null}
# In your ios/Podfile — at the top, before the target block:
didit_sdk_ios_nfc_enabled = ENV.fetch('DIDIT_SDK_IOS_NFC_ENABLED', 'true').downcase != 'false'

def max_ios_version(*versions)
  versions.map(&:to_s).max_by { |version| Gem::Version.new(version) }
end

platform :ios, didit_sdk_ios_nfc_enabled ? max_ios_version(min_ios_version_supported, '15.0') : min_ios_version_supported

# Inside your app target:
didit_sdk_ios_pod = didit_sdk_ios_nfc_enabled ? 'DiditSDK' : 'DiditSDK/Core'
pod didit_sdk_ios_pod, :podspec => 'https://raw.githubusercontent.com/didit-protocol/sdk-ios/main/DiditSDK.podspec'
```

Then install dependencies:

```bash theme={null}
cd ios
bundle exec pod install
```

To rebuild without NFC dependencies, clean CocoaPods and re-run with the env var set:

```bash theme={null}
cd ios
rm -rf Pods Podfile.lock
DIDIT_SDK_IOS_NFC_ENABLED=false bundle exec pod install
```

#### Android Setup

Add the Didit Maven repository to your project-level `settings.gradle`:

```groovy theme={null}
dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
        maven { url "https://raw.githubusercontent.com/didit-protocol/sdk-android/main/repository" }
    }
}
```

Add the following to your app's `android/app/build.gradle` (inside the `android { ... }` block) to resolve BouncyCastle duplicate-class conflicts and the OSGI `MANIFEST.MF` duplicate that the DiditSDK transitive dependencies can trigger:

```groovy theme={null}
android {
    configurations.configureEach {
        exclude group: 'org.bouncycastle', module: 'bcprov-jdk15to18'
        exclude group: 'org.bouncycastle', module: 'bcutil-jdk15to18'
        exclude group: 'org.bouncycastle', module: 'bcpkix-jdk15to18'
        exclude group: 'org.bouncycastle', module: 'bcprov-jdk15on'
    }
    packaging {
        resources {
            pickFirsts += ['org/bouncycastle/**']
            excludes += 'META-INF/versions/9/OSGI-INF/MANIFEST.MF'
        }
    }
}
```

To build without Android NFC dependencies (uses the `me.didit:didit-sdk-core` artifact instead of `me.didit:didit-sdk`), add this to `android/gradle.properties`:

```properties theme={null}
diditSdkAndroidNfcEnabled=false
```

Remove that property, or set it to `true`, to use the full Android SDK with NFC.

***

## Permissions

### iOS

Add the following keys to your app's `Info.plist`. Missing required iOS privacy keys will cause iOS to terminate the app as soon as the SDK accesses that protected resource.

| Permission    | Info.plist Key                        | Description                             | Required     |
| ------------- | ------------------------------------- | --------------------------------------- | ------------ |
| Camera        | `NSCameraUsageDescription`            | Document scanning and face verification | Yes          |
| Microphone    | `NSMicrophoneUsageDescription`        | Liveness video recording                | Yes          |
| Photo Library | `NSPhotoLibraryUsageDescription`      | Upload document images                  | Yes          |
| Location      | `NSLocationWhenInUseUsageDescription` | Geolocation for fraud prevention        | Yes          |
| NFC           | `NFCReaderUsageDescription`           | Read NFC chips in passports/ID cards    | If using NFC |

```xml theme={null}
<key>NSCameraUsageDescription</key>
<string>Camera access is required to scan your identity documents for verification.</string>
<key>NSMicrophoneUsageDescription</key>
<string>Microphone access is required to record video for liveness verification.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Photo library access is required to upload document images.</string>
<key>NFCReaderUsageDescription</key>
<string>NFC is used to read passport chip data for identity verification.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Location access is used to detect your country for identity verification.</string>
```

#### NFC Configuration

To enable NFC reading for passports and ID cards with chips:

1. **Add NFC Capability** in Xcode:
   * Select your target > **Signing & Capabilities** > **+ Capability** > **Near Field Communication Tag Reading**

2. **Add ISO7816 Identifiers** to `Info.plist`:

```xml theme={null}
<key>com.apple.developer.nfc.readersession.iso7816.select-identifiers</key>
<array>
    <string>D23300000045737445494420763335</string>
    <string>A0000002471001</string>
    <string>A0000002472001</string>
    <string>00000000000000</string>
</array>
```

3. **Add an entitlements file** with NFC tag reading enabled:

```xml theme={null}
<key>com.apple.developer.nfc.readersession.formats</key>
<array>
    <string>TAG</string>
</array>
```

Make sure the app's provisioning profile includes the NFC Tag Reading capability. This NFC configuration is not needed when NFC is disabled.

### Android

The following permissions are declared in the SDK's `AndroidManifest.xml` and merged automatically:

| Permission             | Description                             | Required     |
| ---------------------- | --------------------------------------- | ------------ |
| `INTERNET`             | Network access for API communication    | Yes          |
| `ACCESS_NETWORK_STATE` | Detect network availability             | Yes          |
| `CAMERA`               | Document scanning and face verification | Yes          |
| `NFC`                  | Read NFC chips in passports/ID cards    | If using NFC |

Camera and NFC hardware features are declared as optional (`android:required="false"`), so your app can be installed on devices without these features.

***

## Quick Start

```tsx theme={null}
import { startVerification, VerificationStatus } from '@didit-protocol/sdk-react-native';

// Start verification with a session token from your backend
const result = await startVerification('your-session-token');

switch (result.type) {
  case 'completed':
    if (result.session.status === VerificationStatus.Approved) {
      console.log('Identity verified!');
    }
    break;
  case 'cancelled':
    console.log('User cancelled');
    break;
  case 'failed':
    console.log('Error:', result.error.message);
    break;
}
```

***

## Integration Methods

The SDK supports two integration methods:

### Method 1: Session Token (Recommended for Production)

Create a session on your backend using the [Create Verification Session API](/sessions-api/create-session), then pass the token to the SDK:

```tsx theme={null}
import { startVerification } from '@didit-protocol/sdk-react-native';

// Your backend creates a session and returns the token
const sessionToken = await yourBackend.createVerificationSession(userId);

// Pass the token to the SDK
const result = await startVerification(sessionToken);
```

This approach gives you full control over:

* Associating sessions with your users (`vendor_data`)
* Setting custom metadata
* Configuring callbacks per session

### Method 2: Workflow ID (Simpler Integration)

For simpler integrations, the SDK can create sessions directly using your workflow ID:

```tsx theme={null}
import { startVerificationWithWorkflow } from '@didit-protocol/sdk-react-native';

const result = await startVerificationWithWorkflow('your-workflow-id', {
  vendorData: 'user-123',
  contactDetails: { email: 'user@example.com' },
  config: { loggingEnabled: true },
});
```

***

## Configuration

Customize the SDK behavior by passing a `DiditConfig` object:

```tsx theme={null}
const result = await startVerification('your-session-token', {
  languageCode: 'es',           // Force Spanish language
  fontFamily: 'Avenir',         // Custom font (must be registered natively)
  loggingEnabled: true,         // Enable debug logging
  showCloseButton: true,        // Show close (X) button on step screens
  showExitConfirmation: true,   // Confirm before exit
  closeOnComplete: false,       // Auto-dismiss UI on completion
});
```

For `startVerificationWithWorkflow`, pass config inside `options.config`:

```tsx theme={null}
const result = await startVerificationWithWorkflow('your-workflow-id', {
  vendorData: 'user-123',
  config: {
    languageCode: 'es',
    loggingEnabled: true,
    showCloseButton: true,
    showExitConfirmation: true,
  },
});
```

### Configuration Options

| Property               | Type      | Default       | Description                                           |
| ---------------------- | --------- | ------------- | ----------------------------------------------------- |
| `languageCode`         | `string`  | Device locale | ISO 639-1 language code (e.g. `"en"`, `"fr"`, `"ar"`) |
| `fontFamily`           | `string`  | System font   | Custom font family name (must be registered natively) |
| `loggingEnabled`       | `boolean` | `false`       | Enable SDK debug logging                              |
| `showCloseButton`      | `boolean` | `true`        | Show close (X) button on verification step screens    |
| `showExitConfirmation` | `boolean` | `true`        | Show confirmation dialog when user attempts to exit   |
| `closeOnComplete`      | `boolean` | `false`       | Automatically dismiss verification UI when complete   |

<Info>
  **Theming & Colors**: Colors, backgrounds, and intro screen settings are configured through your [White Label settings](https://business.didit.me) in the Didit Console, not in the SDK configuration. This ensures consistent branding across all platforms.
</Info>

***

## Language Support

The SDK supports **53 languages**. If no language is specified, the SDK uses the device locale with English as fallback.

```tsx theme={null}
// Use device locale (default)
await startVerification(token);

// Force specific language
await startVerification(token, { languageCode: 'fr' });
```

**[View All Supported Languages →](/integration/supported-languages)**

***

## Advanced Options

These options are only available with `startVerificationWithWorkflow`, where the SDK creates the session on your behalf.

### Contact Details (Prefill & Notifications)

Provide contact details to prefill verification forms and enable email notifications:

```tsx theme={null}
const result = await startVerificationWithWorkflow('your-workflow-id', {
  contactDetails: {
    email: 'user@example.com',
    sendNotificationEmails: true,  // Send status update emails
    emailLang: 'en',               // Email language (ISO 639-1)
    phone: '+14155552671',         // E.164 format
  },
});
```

### Expected Details (Cross-Validation)

Provide expected user details for automatic cross-validation with extracted document data:

```tsx theme={null}
const result = await startVerificationWithWorkflow('your-workflow-id', {
  expectedDetails: {
    firstName: 'John',
    lastName: 'Doe',
    dateOfBirth: '1990-05-15',     // YYYY-MM-DD format
    gender: 'M',
    nationality: 'USA',            // ISO 3166-1 alpha-3
    country: 'USA',
    address: '1 Market St, San Francisco, CA',
    identificationNumber: 'A1234567',
    ipAddress: '203.0.113.42',
    portraitImage: 'https://...',  // URL or base64 reference image
  },
});
```

All `ExpectedDetails` fields are optional. See [`src/types.ts`](https://github.com/didit-protocol/sdk-react-native/blob/main/src/types.ts) for the full type.

### Custom Metadata

Store custom JSON metadata with the session (not displayed to user):

```tsx theme={null}
const result = await startVerificationWithWorkflow('your-workflow-id', {
  vendorData: 'user-123',
  metadata: '{"internalId": "abc123", "source": "mobile-app"}',
});
```

***

## Handling Results

Both `startVerification` and `startVerificationWithWorkflow` return a `Promise<VerificationResult>`. The result is a discriminated union — use the `type` field to determine the outcome.

### Result Cases

| Case        | Description                                                     |
| ----------- | --------------------------------------------------------------- |
| `completed` | Verification flow completed (check `session.status` for result) |
| `cancelled` | User cancelled the verification flow                            |
| `failed`    | An error occurred during verification                           |

### SessionData Properties

| Property    | Type                 | Description                          |
| ----------- | -------------------- | ------------------------------------ |
| `sessionId` | `string`             | Unique session identifier            |
| `status`    | `VerificationStatus` | `Approved`, `Pending`, or `Declined` |

### Error Types

| Error                | Description                        |
| -------------------- | ---------------------------------- |
| `sessionExpired`     | The session has expired            |
| `networkError`       | Network connectivity issue         |
| `cameraAccessDenied` | Camera permission not granted      |
| `notInitialized`     | SDK not initialized (Android only) |
| `apiError`           | API request failed                 |
| `unknown`            | Other error with message           |

### Complete Result Handling Example

```tsx theme={null}
import {
  startVerification,
  VerificationStatus,
  type VerificationResult,
} from '@didit-protocol/sdk-react-native';

async function verify(token: string) {
  const result: VerificationResult = await startVerification(token);

  switch (result.type) {
    case 'completed':
      switch (result.session.status) {
        case VerificationStatus.Approved:
          console.log('Approved! Session:', result.session.sessionId);
          // User is verified — grant access
          break;
        case VerificationStatus.Pending:
          console.log('Under review. Session:', result.session.sessionId);
          // Show "verification in progress" UI
          break;
        case VerificationStatus.Declined:
          console.log('Declined. Session:', result.session.sessionId);
          // Handle declined verification
          break;
      }
      break;

    case 'cancelled':
      console.log('User cancelled the verification.');
      // Maybe show retry option
      break;

    case 'failed':
      console.log(`Error [${result.error.type}]: ${result.error.message}`);
      // Handle error — show retry or contact support
      break;
  }
}
```

***

## End-to-End Example (Backend Session → React Native SDK → Webhook)

The production-ready integration: your backend creates the session, your RN app receives the `session_token`, and the SDK runs the flow. The final decision is delivered to your backend via webhook — never trust the client-side result alone.

### 1. Backend — create the session

```ts theme={null}
// Node.js / Express example. Run on your server. NEVER ship DIDIT_API_KEY to the device.
import express from "express";

const app = express();
app.use(express.json());

app.post("/api/verification/start", async (req, res) => {
  const response = await fetch("https://verification.didit.me/v3/session/", {
    method: "POST",
    headers: {
      "x-api-key": process.env.DIDIT_API_KEY!,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      workflow_id: process.env.DIDIT_WORKFLOW_ID,
      vendor_data: req.body.userId, // your stable user id
    }),
  });

  if (!response.ok) {
    return res.status(500).json({ error: `Didit ${response.status}` });
  }

  const { session_id, session_token } = await response.json();
  // Persist session_id <-> userId so you can match the webhook later.
  res.json({ session_id, session_token });
});
```

### 2. React Native — exchange and start the SDK

```tsx theme={null}
import { useCallback, useState } from 'react';
import { Button, View } from 'react-native';
import {
  startVerification,
  VerificationStatus,
  type VerificationResult,
} from '@didit-protocol/sdk-react-native';

export function VerifyButton({ userId }: { userId: string }) {
  const [loading, setLoading] = useState(false);

  const onPress = useCallback(async () => {
    setLoading(true);
    try {
      const r = await fetch('https://your-backend.example.com/api/verification/start', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ userId }),
      });
      const { session_token } = await r.json();

      const result: VerificationResult = await startVerification(session_token);

      switch (result.type) {
        case 'completed':
          if (result.session.status === VerificationStatus.Approved) {
            // Optimistic UI; rely on the webhook delivered to your backend as the source of truth.
          }
          break;
        case 'cancelled':
          // User dismissed the flow
          break;
        case 'failed':
          // Show retry — result.error.type / result.error.message
          break;
      }
    } finally {
      setLoading(false);
    }
  }, [userId]);

  return (
    <View>
      <Button title={loading ? 'Starting...' : 'Verify identity'} onPress={onPress} disabled={loading} />
    </View>
  );
}
```

### 3. Backend — receive the final decision via webhook

```ts theme={null}
app.post("/api/webhooks/didit", express.raw({ type: "application/json" }), (req, res) => {
  // 1. Verify the X-Signature header (see Webhooks docs)
  // 2. Parse the body
  const event = JSON.parse(req.body.toString());
  if (event.webhook_type === "status.updated") {
    // event.session_id, event.status ("Approved" | "Declined" | "In Review" | ...)
    // Look up the user by session_id and update their verification state.
  }
  res.sendStatus(200);
});
```

See the [Webhooks guide](/integration/webhooks) for HMAC signature verification details.

***

## Complete Example

```tsx theme={null}
import { useState, useCallback } from 'react';
import {
  Text,
  View,
  TextInput,
  TouchableOpacity,
  Alert,
  ActivityIndicator,
  SafeAreaView,
} from 'react-native';
import {
  startVerification,
  VerificationStatus,
  type VerificationResult,
} from '@didit-protocol/sdk-react-native';

export default function App() {
  const [token, setToken] = useState('');
  const [loading, setLoading] = useState(false);

  const handleVerify = useCallback(async () => {
    if (!token.trim()) {
      Alert.alert('Error', 'Please enter a session token.');
      return;
    }

    setLoading(true);
    try {
      const result = await startVerification(token.trim(), {
        loggingEnabled: true,
      });

      switch (result.type) {
        case 'completed':
          Alert.alert(
            'Verification Complete',
            `Status: ${result.session.status}\nSession: ${result.session.sessionId}`
          );
          break;
        case 'cancelled':
          Alert.alert('Cancelled', 'The user cancelled the verification.');
          break;
        case 'failed':
          Alert.alert('Failed', `${result.error.type}: ${result.error.message}`);
          break;
      }
    } catch (error) {
      Alert.alert('Error', `Unexpected error: ${error}`);
    } finally {
      setLoading(false);
    }
  }, [token]);

  return (
    <SafeAreaView style={{ flex: 1, padding: 24, justifyContent: 'center' }}>
      <TextInput
        placeholder="Enter session token..."
        value={token}
        onChangeText={setToken}
        autoCapitalize="none"
        style={{
          borderWidth: 1,
          borderColor: '#ccc',
          borderRadius: 8,
          padding: 12,
          marginBottom: 16,
        }}
      />
      <TouchableOpacity
        onPress={handleVerify}
        disabled={loading}
        style={{
          backgroundColor: '#1a1a1a',
          borderRadius: 8,
          padding: 16,
          alignItems: 'center',
          opacity: loading ? 0.6 : 1,
        }}
      >
        {loading ? (
          <ActivityIndicator color="#fff" />
        ) : (
          <Text style={{ color: '#fff', fontWeight: '600' }}>
            Start Verification
          </Text>
        )}
      </TouchableOpacity>
    </SafeAreaView>
  );
}
```

***
