React Native SDK
The Kora IDV React Native SDK is a thin JS wrapper over the native iOS and Android SDKs. All camera, ML, and verification UI runs natively for performance and access to platform capabilities (CameraX, AVFoundation, ML Kit, NFC). Your JS layer just calls a single bridge method and gets back a typed result.
Requirements
- React Native 0.72+ (Old or New Architecture)
- iOS 14.0+ deployment target
- Android
minSdk24+ - Bare React Native CLI or Expo with prebuild (
expo prebuild). Expo Go is not supported — the SDK uses native modules.
Installation
npm install @koraidv/react-native
# or
yarn add @koraidv/react-native
iOS
cd ios && pod install
Add the camera permission to ios/<YourApp>/Info.plist:
<key>NSCameraUsageDescription</key>
<string>We need your camera to verify your identity.</string>
If you also enable NFC passport reading, add:
<key>NFCReaderUsageDescription</key>
<string>We use NFC to verify the chip in your passport.</string>
<key>com.apple.developer.nfc.readersession.iso7816.select-identifiers</key>
<array>
<string>A0000002471001</string>
</array>
Android
The package autolinks. After installing, rebuild:
npx react-native run-android
Add to android/app/src/main/AndroidManifest.xml:
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-feature android:name="android.hardware.camera" android:required="true" />
The SDK requests camera permission at runtime if not already granted.
Expo
If you use Expo with custom dev clients (not Expo Go):
npx expo install @koraidv/react-native
npx expo prebuild
npx expo run:ios
npx expo run:android
The package ships an Expo config plugin that wires the iOS Info.plist keys and Android manifest entries automatically.
Quick start (React)
The recommended path is the KoraIDVProvider + useKoraIDV hook combination. Wrap the part of your app that needs verification:
import { KoraIDVProvider } from '@koraidv/react-native';
export default function App() {
return (
<KoraIDVProvider
apiKey="kora_sandbox_xxxxx"
tenantId="your-tenant-uuid"
config={{ environment: 'sandbox' }}
>
<RootNavigator />
</KoraIDVProvider>
);
}
Then trigger verification from any screen:
import { useKoraIDV } from '@koraidv/react-native';
function VerifyScreen() {
const {
startVerification,
verification,
error,
isLoading,
isCancelled,
reset,
} = useKoraIDV();
const onPress = () => {
startVerification(
`user-${userId}`, // your own external identifier
'standard', // tier
);
};
if (isLoading) return <Text>Verifying…</Text>;
if (verification) {
return (
<View>
<Text>Status: {verification.status}</Text>
<Text>Risk score: {verification.riskScore}</Text>
</View>
);
}
if (error) {
return (
<View>
<Text>Error: {error.code}</Text>
<Button title="Retry" onPress={reset} />
</View>
);
}
if (isCancelled) {
return (
<View>
<Text>You cancelled. Try again?</Text>
<Button title="Restart" onPress={reset} />
</View>
);
}
return <Button title="Verify identity" onPress={onPress} />;
}
Headless component
For a one-shot flow with callbacks, use <VerificationFlow> — it auto-starts on mount and renders nothing in JS:
import { KoraIDVProvider, VerificationFlow } from '@koraidv/react-native';
function VerifyOnce() {
return (
<KoraIDVProvider apiKey="…" tenantId="…">
<VerificationFlow
externalId={`user-${userId}`}
tier="standard"
onComplete={(v) => console.log('done', v.status, v.riskScore)}
onError={(e) => console.error(e.code, e.message)}
onCancel={() => console.log('user cancelled')}
/>
</KoraIDVProvider>
);
}
Imperative API
For non-React contexts (sagas, redux thunks, native code paths), use the singleton:
import { KoraIDV } from '@koraidv/react-native';
KoraIDV.configure({
apiKey: 'kora_sandbox_xxxxx',
tenantId: 'your-tenant-uuid',
environment: 'sandbox',
});
const result = await KoraIDV.startVerification(`user-${userId}`, 'standard');
if (result.type === 'success') {
console.log(result.verification.status);
} else {
console.log('cancelled');
}
Configuration
type KoraIDVConfiguration = {
apiKey: string; // kora_sandbox_… or kora_live_…
tenantId: string; // your tenant UUID
environment?: 'sandbox' | 'production'; // auto-detected from key prefix
baseUrl?: string; // override (on-prem only)
livenessMode?: 'active' | 'passive';
theme?: Theme;
timeout?: number; // network timeout in ms (default 120000)
debugLogging?: boolean;
};
Pass anything beyond apiKey and tenantId via the config prop on the provider:
<KoraIDVProvider
apiKey="…"
tenantId="…"
config={{
environment: 'production',
livenessMode: 'active',
theme: { primaryColor: '#0D9488', backgroundColor: '#FFFFFF' },
debugLogging: __DEV__,
}}
>
<App />
</KoraIDVProvider>
Verification tiers
| Tier | Includes |
|---|---|
basic | Document OCR + basic authenticity |
standard | + Face match + active liveness |
enhanced | + Anti-spoof + risk signals + compliance screening (sanctions / PEP / adverse media) |
Pick the tier that matches the regulatory requirement for the user action.
Result shape
type Verification = {
id: string;
externalId: string;
tenantId: string;
tier: string;
status:
| 'pending'
| 'document_required'
| 'selfie_required'
| 'liveness_required'
| 'processing'
| 'approved'
| 'rejected'
| 'review_required'
| 'expired';
documentVerification: DocumentVerification | null;
faceVerification: FaceVerification | null;
livenessVerification: LivenessVerification | null;
riskSignals: RiskSignal[] | null;
riskScore: number | null; // 0–100 overall
createdAt: string;
updatedAt: string;
completedAt: string | null;
};
Per-feature scores live inside the nested objects: documentVerification.documentAuthenticity, faceVerification.matchScore, livenessVerification.score, etc. The single top-level riskScore is the fused 0–100 number you'd typically display.
The complete VerificationFlowResult is a tagged union:
type VerificationFlowResult =
| { type: 'success'; verification: Verification }
| { type: 'cancelled' };
Errors (network failures, denied permissions, expired sessions) are thrown as KoraError from startVerification / resumeVerification.
Resuming a verification
If a user closes your app mid-flow, you'll get a verificationId from your webhook (or from a previous run's verification.id). Resume with:
const { resumeVerification } = useKoraIDV();
await resumeVerification(existingVerificationId);
Error handling
import { KoraError, KoraErrorCode } from '@koraidv/react-native';
try {
await KoraIDV.startVerification(externalId, 'standard');
} catch (err) {
if (err instanceof KoraError) {
switch (err.code) {
case KoraErrorCode.NETWORK_ERROR:
showRetry(err.message);
break;
case KoraErrorCode.CAMERA_ACCESS_DENIED:
promptCameraPermission();
break;
case KoraErrorCode.SESSION_EXPIRED:
// Start a fresh verification
break;
case KoraErrorCode.NOT_CONFIGURED:
// KoraIDV.configure was never called
break;
}
}
}
ProGuard / R8 rules (Android)
If R8 is enabled in your release build, add to android/app/proguard-rules.pro:
-keep class com.koraidv.sdk.** { *; }
-keepclassmembers class com.koraidv.sdk.** { *; }
Server-side webhook
The mobile flow returns the verification result directly, but you should also configure a webhook endpoint to receive the authoritative verification.completed event server-side (mobile clients can't be trusted to report failures). See Webhooks.
Wallet (W3C Verifiable Credentials)
The SDK also ships a wallet module for storing and presenting credentials with selective disclosure. Import from the same package:
import { KoraWallet, DisclosureProfiles } from '@koraidv/react-native';
Wallet documentation: see the KoraWallet API reference.
Common issues
| Issue | Fix |
|---|---|
The package '@koraidv/react-native' doesn't seem to be linked | Run pod install (iOS) and rebuild (npx react-native run-android / run-ios). Don't use Expo Go. |
| Black camera preview on Android | Confirm <uses-permission android:name="android.permission.CAMERA" /> is in your manifest and runtime permission was granted. |
NOT_CONFIGURED error | KoraIDV.configure() (or wrapping in KoraIDVProvider) must complete before any startVerification call. |
| Sandbox API key returning 401 | Verify the key is kora_sandbox_… and you didn't override environment to 'production'. |
| iOS build fails with "Module 'KoraIDV' was not built for...": | Clean the iOS derived data: cd ios && rm -rf build Pods Podfile.lock && pod install. |
Next steps
- Sandbox testing — deterministic test fixtures
- Webhooks — server-side delivery of verification events
- Server integration — verifying API key + tenant context on your backend
- Image retrieval — accessing persisted document and selfie images for compliance review