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

GitHub Repository

View source code and examples on GitHub

npm Package

@didit-protocol/sdk-react-native

Requirements

RequirementMinimum Version
React Native0.76+ (New Architecture / TurboModules)
Node.js20+
TypeScript5+

Platform Requirements

PlatformMinimum VersionNotes
iOS13.0+NFC passport reading requires iOS 15.0+
AndroidAPI 24+ (7.0)Kotlin 1.9+, Java 17+

Installation

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:
{
  "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:
{
  "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.
This SDK uses native modules (camera, NFC) that are not available in Expo Go. You must use a development build or run npx expo prebuild to generate the native projects.

React Native CLI

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:
# 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:
cd ios
bundle exec pod install
To rebuild without NFC dependencies, clean CocoaPods and re-run with the env var set:
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:
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:
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:
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.
PermissionInfo.plist KeyDescriptionRequired
CameraNSCameraUsageDescriptionDocument scanning and face verificationYes
MicrophoneNSMicrophoneUsageDescriptionLiveness video recordingYes
Photo LibraryNSPhotoLibraryUsageDescriptionUpload document imagesYes
LocationNSLocationWhenInUseUsageDescriptionGeolocation for fraud preventionYes
NFCNFCReaderUsageDescriptionRead NFC chips in passports/ID cardsIf using NFC
<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:
<key>com.apple.developer.nfc.readersession.iso7816.select-identifiers</key>
<array>
    <string>D23300000045737445494420763335</string>
    <string>A0000002471001</string>
    <string>A0000002472001</string>
    <string>00000000000000</string>
</array>
  1. Add an entitlements file with NFC tag reading enabled:
<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:
PermissionDescriptionRequired
INTERNETNetwork access for API communicationYes
ACCESS_NETWORK_STATEDetect network availabilityYes
CAMERADocument scanning and face verificationYes
NFCRead NFC chips in passports/ID cardsIf 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

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: Create a session on your backend using the Create Verification Session API, then pass the token to the SDK:
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:
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:
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:
const result = await startVerificationWithWorkflow('your-workflow-id', {
  vendorData: 'user-123',
  config: {
    languageCode: 'es',
    loggingEnabled: true,
    showCloseButton: true,
    showExitConfirmation: true,
  },
});

Configuration Options

PropertyTypeDefaultDescription
languageCodestringDevice localeISO 639-1 language code (e.g. "en", "fr", "ar")
fontFamilystringSystem fontCustom font family name (must be registered natively)
loggingEnabledbooleanfalseEnable SDK debug logging
showCloseButtonbooleantrueShow close (X) button on verification step screens
showExitConfirmationbooleantrueShow confirmation dialog when user attempts to exit
closeOnCompletebooleanfalseAutomatically dismiss verification UI when complete
Theming & Colors: Colors, backgrounds, and intro screen settings are configured through your White Label settings in the Didit Console, not in the SDK configuration. This ensures consistent branding across all platforms.

Language Support

The SDK supports 53 languages. If no language is specified, the SDK uses the device locale with English as fallback.
// Use device locale (default)
await startVerification(token);

// Force specific language
await startVerification(token, { languageCode: 'fr' });
View All 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:
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:
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 for the full type.

Custom Metadata

Store custom JSON metadata with the session (not displayed to user):
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

CaseDescription
completedVerification flow completed (check session.status for result)
cancelledUser cancelled the verification flow
failedAn error occurred during verification

SessionData Properties

PropertyTypeDescription
sessionIdstringUnique session identifier
statusVerificationStatusApproved, Pending, or Declined

Error Types

ErrorDescription
sessionExpiredThe session has expired
networkErrorNetwork connectivity issue
cameraAccessDeniedCamera permission not granted
notInitializedSDK not initialized (Android only)
apiErrorAPI request failed
unknownOther error with message

Complete Result Handling Example

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

// 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

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

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 for HMAC signature verification details.

Complete Example

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>
  );
}