Skip to main content

React Native SDK

JWT-based identity verification SDK for React Native with dynamic workflow support.

Features

  • 🔐 JWT-Based Configuration - Initialize SDK with a single JWT token containing all configuration
  • 📸 Automatic ID Cropping - Precise geometric mapping to crop ID cards to exact dimensions (590x372px)
  • 📷 High-Quality Capture - Maximum quality (95%) with accurate dimension detection for sharp images
  • 🎯 Accurate Cropping - Real photo dimensions used for geometric mapping (no more hardcoded values)
  • 🎭 Dynamic Workflows - Conditionally capture selfie based on backend workflow configuration
  • 🎨 Highly Customizable - Customize colors, logos, images, messages, and button text
  • 🌐 Backend Branding Control - Full branding control via react_native_sdk_configs in workflow
  • 📦 Compression Built-in - Automatic payload compression using pako for bandwidth optimization
  • TypeScript Support - Full TypeScript support with comprehensive type definitions
  • Expo Compatible - Works seamlessly with Expo-managed projects
  • 🚀 Auto-Start Demo Mode - Perfect for marketing portals and demos

Installation

npm install nafsi-react-native
# or
yarn add nafsi-react-native

Peer Dependencies

Install required peer dependencies:

expo install expo-camera expo-image-manipulator expo-file-system react-native-svg

Quick Start

import React from 'react';
import {View} from 'react-native';
import NafsiVerification from 'nafsi-react-native';

export default function App() {
const jwtToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."; // From your backend

return (
<View style={{flex: 1}}>
<NafsiVerification
token={jwtToken}
onSuccess={(result) => {
console.log('Verification successful!', result);
// Navigate to success screen or handle result
}}
onError={(error) => {
console.error('Verification failed:', error);
// Show error message or retry
}}
onCancel={() => {
console.log('User cancelled verification');
// Navigate back
}}
/>
</View>
);
}

JWT Token Format

The SDK expects a JWT token with the following payload structure:

{
"workflowId": "your-workflow-id",
"clientId": "your-client-id",
"organisationId": 14,
"apiUrl": "https://apisv2.windeal.co.ke/postdata",
"refresh_token": "your-refresh-token",
"config": "kenya_national_id",
"iat": 1767688286,
"exp": 1767691886
}

Configuration Priority

The SDK uses a three-tier configuration system with the following priority order:

  1. Default values (lowest priority) - Built-in SDK defaults
  2. Developer customization (medium priority) - Props passed to <NafsiVerification>
  3. Workflow database config (highest priority) - Configuration from getWorkflowById API response

This means that configuration from your workflow's react_native_sdk_configs or web_sdk_configs will override any customization you provide via props.

Workflow SDK Config Example

When you call getWorkflowById, the response may include SDK configuration:

{
"workflow": {
"id": "workflow-123",
"react_native_sdk_configs": {
"logo": "https://example.com/logo.png",
"title": "Welcome to Naweza Africa",
"tagline": "Your leading Home Service Market in Africa!",
"hero_image": "https://example.com/hero.png",
"background_image": "https://example.com/background.jpg",
"colors": {
"primary": "#9333ea",
"secondary": "#6b21a8",
"text": "#1a1a1a",
"background": "#dbeafe"
}
}
}
}

This workflow config will automatically override your prop-based customization.

Customization

Theme Colors

<NafsiVerification
token={jwtToken}
customization={{
theme: {
primaryColor: '#00b8ff',
secondaryColor: '#17D27C',
accentColor: '#fa764a',
backgroundColor: '#ffffff',
textColor: '#333333',
buttonTextColor: '#ffffff',
}
}}
/>

Landing Page

<NafsiVerification
token={jwtToken}
customization={{
landing: {
backgroundImage: 'https://example.com/background.jpg',
logoUrl: 'https://example.com/logo.png',
heroMessage: 'Verify Your Identity',
tagline: 'Quick and secure verification in minutes',
getStartedButtonText: 'Start Verification',
}
}}
/>

Messages

<NafsiVerification
token={jwtToken}
customization={{
messages: {
idFrontTitle: 'Capture ID Front',
idFrontSubtitle: 'Position your ID card within the frame',
idBackTitle: 'Capture ID Back',
idBackSubtitle: 'Position the back of your ID within the frame',
selfieTitle: 'Take a Selfie',
selfieSubtitle: 'Position your face within the oval',
reviewTitle: 'Review Your Captures',
startVerificationButton: 'Submit Verification',
retakeButton: 'Retake',
continueButton: 'Continue',
}
}}
/>

Camera Settings

<NafsiVerification
token={jwtToken}
customization={{
camera: {
idFrameColor: '#17D27C',
selfieOutlineColor: '#17D27C',
captureButtonColor: '#00b8ff',
}
}}
/>

API Reference

NafsiConfig Props

PropTypeRequiredDescription
tokenstringYesJWT token containing workflow and API configuration
customizationNafsiCustomizationNoUI/UX customization options
onSuccess(result: VerificationResult) => voidNoCallback fired when verification succeeds
onError(error: NafsiError) => voidNoCallback fired when verification fails
onCancel() => voidNoCallback fired when user cancels verification
onStepChange(step: VerificationStep) => voidNoCallback fired when user moves to a different step

VerificationResult

interface VerificationResult {
status: 'success' | 'failed' | 'pending';
verification_id?: string;
extracted_data?: {
full_name?: string;
id_number?: string;
date_of_birth?: string;
place_of_birth?: string;
gender?: string;
};
face_match?: {
match: boolean;
confidence: number;
};
liveness?: {
is_live: boolean;
confidence: number;
};
selfie_url?: string;
}

Advanced Usage

Using Hooks Directly

For custom flows, you can use the hooks directly:

import {useNafsiFlow} from 'nafsi-react-native';

function CustomVerificationFlow() {
const {
currentStep,
requiresSelfie,
mergedCustomization, // Final merged configuration
images,
captureImage,
submitVerification,
isSubmitting,
} = useNafsiFlow({
token: jwtToken,
onSuccess: handleSuccess,
onError: handleError,
});

// Access the final merged configuration
console.log('Using theme:', mergedCustomization.theme);
console.log('Using landing config:', mergedCustomization.landing);

// Custom rendering logic
return <YourCustomUI/>;
}

Understanding Configuration Merging

The SDK provides utilities to manually merge configurations if needed:

import {mergeConfigurations, getWorkflowSdkConfig} from 'nafsi-react-native';

// Get SDK config from workflow
const workflowConfig = getWorkflowSdkConfig(workflow);

// Merge with your customization
const merged = mergeConfigurations(
{
theme: {primaryColor: '#00b8ff'},
landing: {heroMessage: 'My Custom Message'}
},
workflowConfig
);

console.log('Merged config:', merged);
// Workflow config takes priority over your customization

Using Services

Access low-level services for advanced use cases:

import {
ApiService,
TokenService,
WorkflowService,
CompressionService,
} from 'nafsi-react-native';

const apiService = new ApiService('https://api.example.com');
const tokenService = new TokenService(apiService);
const workflowService = new WorkflowService(apiService);

// Decode JWT
const decoded = tokenService.decodeJWT(jwtToken);

// Refresh access token
const accessToken = await tokenService.refreshAccessToken(
decoded.refresh_token,
decoded.organisationId
);

// Fetch workflow
const workflow = await workflowService.fetchWorkflow(
decoded.ss,
decoded.clientId,
accessToken,
decoded.organisationId
);

// Check if selfie is required
const requiresSelfie = workflowService.requiresSelfie(workflow);

// Compress data
const compressed = CompressionService.compress({data: 'example'});

Workflow Configuration

The SDK dynamically determines which steps to show based on the workflow fetched from your backend.

Selfie Capture Conditions

Selfie capture is conditionally shown based on workflow steps:

  • If workflow includes face_matching, liveness_detection, or age_verification → Selfie is required
  • Otherwise → Selfie is skipped

Example workflow response:

{
"workflow": {
"id": "workflow-123",
"name": "KYC Verification",
"steps": [
{
"name": "OCR Extraction",
"order": 1,
"required": true
},
{
"name": "Face Matching",
"order": 2,
"required": false
},
{
"name": "Liveness Detection",
"order": 3,
"required": false
}
]
}
}

In this example, selfie would be captured because Face Matching and Liveness Detection are present.

Image Processing

ID Card Cropping

  • Maximum quality capture - 100% quality (quality: 1.0)
  • Actual dimension detection - Uses real photo dimensions from camera
  • Accurate geometric mapping - Maps screen overlay to exact photo coordinates
  • Automatic cropping to green frame overlay
  • Output dimensions: Exactly 590×372 pixels (ISO/IEC 7810 ID-1 standard)
  • High compression: JPEG at 95% quality (COMPRESS_QUALITY: 0.95)

How it works:

  1. Camera captures photo at native resolution (e.g., 4032×3024)
  2. Photo dimensions are stored in state
  3. Geometric algorithm maps green frame position to photo coordinates
  4. Image cropped to exact frame area
  5. Resized to standard 590×372px
  6. Compressed at 95% quality

Selfie Processing

  • Cropping to outline - Crops to the oval outline shown on screen
  • Geometric mapping - Same accurate algorithm as ID cards
  • Resizing to 590px width (maintains aspect ratio)
  • High compression: JPEG at 95% quality

Selfie outline dimensions:

  • Screen overlay: 280×350 pixels (aspect ratio 0.8)
  • Crops to this shape from full captured photo

Base64 Encoding

All images are converted to base64 data URLs:

data:image/jpeg;base64,/9j/4AAQSkZJRg...

Error Handling

<NafsiVerification
token={jwtToken}
onError={(error) => {
switch (error.code) {
case 'INVALID_JWT':
console.error('Invalid JWT token');
break;
case 'TOKEN_REFRESH_FAILED':
console.error('Failed to refresh access token');
break;
case 'WORKFLOW_FETCH_FAILED':
console.error('Failed to fetch workflow');
break;
case 'CAMERA_PERMISSION_DENIED':
console.error('Camera permission denied');
break;
case 'VERIFICATION_FAILED':
console.error('Verification submission failed');
break;
default:
console.error('Unknown error:', error.message);
}
}}
/>

API Integration

Initialization Flow

  1. Decode JWT → Extract workflowId, clientId, apiUrl, organisationId, refresh_token
  2. Refresh Access Token → POST /postdata with menu: 'auth_config', method: 'refreshAccessToken'
  3. Fetch Workflow → POST /postdata with menu: 'flow', method: 'getWorkflowById'
  4. Parse Steps → Determine if selfie is required
  5. Show Landing → Start verification flow

Verification Submission

  1. Build Payload → Include client_id, work_flow_id, ID images, optional selfie
  2. Compress Payload → Use pako.deflateRaw (level 9) → base64 encode
  3. Submit → POST /postdata with menu: 'verification', method: 'submitVerification'
  4. Handle Response → Call onSuccess or onError

TypeScript Support

The SDK is fully typed. Import types as needed:

import type {
NafsiConfig,
VerificationResult,
NafsiError,
Workflow,
DecodedJWT,
} from 'nafsi-react-native';

Demo Mode & Marketing Portal

The SDK includes an auto-start demo mode perfect for marketing portals and sales presentations.

Enable auto-start in example app:

// example/App.tsx
const AUTO_START_DEMO = true; // SDK starts automatically
const DEMO_TOKEN = `your-long-lived-demo-token`;

Deploy demo to web:

cd example
npx expo export:web
vercel --prod

Result: Instant live demo at https://your-demo.vercel.app

Embedding in Marketing Portal

<iframe
src="https://nafsi-demo.vercel.app"
style="width: 100%; height: 800px; border: 0;"
></iframe>

Perfect for showcasing SDK capabilities to prospects without requiring them to configure tokens.

Requirements

  • React Native >= 0.60.0
  • Expo SDK >= 49.0.0 (if using Expo)
  • iOS >= 13.0
  • Android >= 6.0 (API level 23)

License

MIT

Support