Skip to main content

Nafsi Web SDK v2.0.0

Design-Agnostic Identity Verification SDK - Pure Vanilla JavaScript SDK for seamless ID verification in any application.

✨ Key Features

  • Design-Agnostic - Inherits your app's design system automatically (Material UI, Ant Design, Bootstrap, or custom)
  • Embeddable - Renders inline within your containers, not just fullscreen modals
  • Zero Dependencies - Pure vanilla JavaScript, no React/Vue/Angular required
  • Lightweight - Only ~24KB minified (~8KB gzipped)
  • High Quality Capture - Professional-grade image processing (1014×640 for IDs, 590×372 for selfies)
  • Auto Token Refresh - Automatic JWT token refresh with localStorage management
  • Framework Detection - Automatically detects and inherits CSS variables from popular frameworks
  • Three Style Modes - inherit (default), headless, or standalone
  • Responsive - Works seamlessly on desktop and mobile browsers
  • Customizable - Full control over branding, colors, and behavior

🚀 Quick Start

CDN Integration (Simplest)

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Identity Verification</title>
</head>
<body>
<div id="verification-container"></div>

<script src="https://sdk.nafsi.ai/v2/nafsi.js"></script>
<script>
async function startVerification() {
await Nafsi.init({
token: 'YOUR_JWT_TOKEN',
container: '#verification-container',
debug: true
});

Nafsi.start();
}

startVerification();
</script>
</body>
</html>

📦 Installation Options

<script src="https://sdk.nafsi.ai/v2/nafsi.js"></script>

Option 2: NPM Package (Coming Soon)

npm install @nafsi/web-sdk
import Nafsi from '@nafsi/web-sdk';

Option 3: Self-Hosted

Download dist/nafsi.js and host it on your server:

<script src="/path/to/nafsi.js"></script>

🎨 Design-Agnostic Integration

The Nafsi SDK is design-agnostic by default, meaning it automatically inherits your application's design system without requiring any additional configuration.

How It Works

The SDK uses CSS custom properties (variables) to inherit colors, fonts, and spacing from your parent application:

/* SDK automatically looks for these variables in your app */
--custom-primary /* Primary brand color */
--custom-secondary /* Secondary/accent color */
--custom-text /* Text color */
--custom-background /* Background color */
--custom-font-family /* Font family */

Framework Auto-Detection

The SDK automatically detects and inherits from popular frameworks:

1. Material UI (MUI)

// Your Material UI app
import { ThemeProvider, createTheme } from '@mui/material/styles';

const theme = createTheme({
palette: {
primary: { main: '#1976d2' },
secondary: { main: '#dc004e' }
}
});

// Nafsi SDK automatically inherits MUI theme
await Nafsi.init({
token: 'YOUR_TOKEN',
container: '#verification-container',
styleMode: 'headless', // Use your custom CSS
styles: './my-nafsi-styles.css'
});

SDK detects: --mui-palette-primary-main, --mui-palette-secondary-main

2. Ant Design

// Your Ant Design app
import { ConfigProvider } from 'antd';

<ConfigProvider theme={{
token: {
colorPrimary: '#1890ff',
colorSecondary: '#52c41a'
}
}}>
{/* Your app */}
</ConfigProvider>

// Nafsi SDK automatically inherits Ant Design theme
await Nafsi.init({
token: 'YOUR_TOKEN',
container: '#verification-container',
styleMode: 'headless',
styles: './my-nafsi-styles.css'
});

SDK detects: --ant-primary-color, --ant-secondary-color

3. Bootstrap

<!-- Your Bootstrap app -->
<link rel="stylesheet" href="bootstrap.min.css">
<style>
:root {
--bs-primary: #0d6efd;
--bs-secondary: #6c757d;
}
</style>

<script>
// Nafsi SDK automatically inherits Bootstrap variables
await Nafsi.init({
token: 'YOUR_TOKEN',
container: '#verification-container',
styleMode: 'inherit' // Uses Bootstrap variables
});
</script>

SDK detects: --bs-primary, --bs-secondary

4. Custom Design System

<style>
:root {
--custom-primary: #f97316;
--custom-secondary: #ea580c;
--custom-text: #1a1a1a;
--custom-font-family: 'Inter', sans-serif;
}
</style>

<script>
// SDK automatically uses your custom variables
await Nafsi.init({
token: 'YOUR_TOKEN',
container: '#verification-container'
});
</script>

🎛️ Configuration Reference

Nafsi.init(options) - All Options

await Nafsi.init({
// ============================================
// REQUIRED
// ============================================

/**
* JWT token containing workflow configuration
* Generated by your backend with embedded config
* @type {string}
* @required
*/
token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',

// ============================================
// CONTAINER & DISPLAY MODE
// ============================================

/**
* CSS selector for container element
* If provided, SDK renders inline (embedded mode)
* If omitted, SDK creates a modal overlay
* @type {string}
* @default null
* @example '#verification-container' | '.verification-box'
*/
container: '#verification-container',

/**
* Enable fullscreen modal mode
* Only relevant when container is not specified
* @type {boolean}
* @default false
*/
fullscreen: false,

/**
* Loading mode for SDK UI
* @type {'inline' | 'modal' | 'new-page'}
* @default 'modal' (or 'inline' if container is specified)
*
* - 'inline': Renders inside specified container
* - 'modal': Creates modal overlay on page
* - 'new-page': Redirects to hosted verification page
*/
loadingMode: 'inline',

/**
* Verification page URL for new-page mode
* @type {string}
* @default window.location.origin + '/v2/verify'
*/
verificationPageUrl: 'https://sdk.nafsi.ai/v2/verify',

// ============================================
// STYLING & THEMING
// ============================================

/**
* Style mode - controls how CSS is applied
* @type {'inherit' | 'headless' | 'standalone'}
* @default 'inherit'
*
* - 'inherit': Minimal CSS, inherits from parent (best for embedded)
* - 'headless': No default CSS, bring your own styles
* - 'standalone': Full CSS bundle, self-contained UI
*/
styleMode: 'inherit',

/**
* Custom CSS file(s) to load
* Use with styleMode: 'headless' for complete control
* @type {string | string[]}
* @default null
* @example './my-nafsi-styles.css' | ['./base.css', './theme.css']
*/
styles: './my-nafsi-styles.css',

/**
* Customization options for branding and theme
* @type {object}
*/
customization: {
/**
* Landing page customization
*/
landing: {
skip: false, // Skip landing page (go straight to camera)
getStartedButtonText: 'Get Started', // Button text
learnMoreButtonText: 'Learn More', // Secondary button text
learnMoreUrl: null, // URL for learn more button
},

/**
* Theme customization (overrides inherited values)
*/
theme: {
primaryColor: '#f97316', // Primary brand color
secondaryColor: '#ea580c', // Secondary/accent color
successColor: '#17D27C', // Success state color
errorColor: '#FF4842', // Error state color
background: '#ffffff', // Background color
cardBackground: '#f5f5f5', // Card/container background
textColor: '#1a1a1a', // Text color
borderRadius: '8px', // Border radius
shadow: '0 2px 8px rgba(0,0,0,0.1)', // Box shadow
fontFamily: 'Inter, sans-serif', // Font family
buttonPadding: '12px 24px', // Button padding
buttonRadius: '8px', // Button border radius
spacing: '1rem', // Base spacing unit
spacing1: '0.5rem', // Small spacing
spacing2: '1rem', // Medium spacing
spacing3: '1.5rem', // Large spacing
spacingMobile: '0.75rem', // Mobile spacing
}
},

// ============================================
// BRANDING (Legacy - use customization.theme instead)
// ============================================

/**
* Logo URL
* @type {string}
* @default 'https://via.placeholder.com/200x80?text=Logo'
*/
logo: 'https://yourcompany.com/logo.png',

/**
* Hero image URL
* @type {string}
* @default 'https://via.placeholder.com/400x300?text=Verify+Identity'
*/
heroImage: 'https://yourcompany.com/hero.png',

/**
* Background image URL (for landing page)
* @type {string}
* @default null
*/
backgroundImage: 'https://yourcompany.com/bg.jpg',

/**
* Landing page title
* @type {string}
* @default 'Verify Your Identity'
*/
title: 'Verify Your Identity',

/**
* Landing page tagline
* @type {string}
* @default 'Quick and secure identity verification'
*/
tagline: 'Quick and secure verification',

/**
* Primary color (hex)
* @type {string}
* @default '#0d153b'
*/
primaryColor: '#1976d2',

/**
* Secondary color (hex)
* @type {string}
* @default '#00b8ff'
*/
secondaryColor: '#dc004e',

// ============================================
// WORKFLOW & BEHAVIOR
// ============================================

/**
* Maximum number of capture retries
* Automatically fetched from workflow config
* @type {number}
* @default 3
*/
maxRetries: 3,

/**
* Workflow steps (automatically fetched)
* SDK fetches this from API, no need to provide
* @type {array}
* @default []
*/
workflowSteps: [],

/**
* Enable debug logging
* @type {boolean}
* @default false
*/
debug: true,

/**
* Enable automatic token refresh
* @type {boolean}
* @default true
*/
autoRefresh: true,

// ============================================
// CALLBACKS
// ============================================

/**
* Called when verification completes successfully
* @type {function}
* @param {object} result - Verification result
*/
onSuccess: (result) => {
console.log('Verification successful:', result);
},

/**
* Called when verification fails
* @type {function}
* @param {object} error - Error details
*/
onFailure: (error) => {
console.error('Verification failed:', error);
},

/**
* Called when token is refreshed
* @type {function}
* @param {object} tokens - New tokens { access_token, refresh_token }
*/
onTokenRefresh: (tokens) => {
console.log('Token refreshed:', tokens);
},

/**
* Called on each step/state change
* @type {function}
* @param {string} step - Current step name
*/
onStepChange: (step) => {
console.log('Step changed:', step);
},

/**
* Called when image is captured
* @type {function}
* @param {object} capture - { type, imageData }
*/
onCapture: (capture) => {
console.log('Image captured:', capture.type);
},

/**
* Called when verification flow completes (modal mode)
* @type {function}
* @param {object} result - Completion result
*/
onComplete: (result) => {
console.log('Flow complete:', result);
},

/**
* Called on any error (modal mode)
* @type {function}
* @param {object} error - Error object
*/
onError: (error) => {
console.error('Error:', error);
},

// ============================================
// ADVANCED
// ============================================

/**
* Custom component renderers
* Advanced: Override default UI components
* @type {object}
* @default {}
*/
customRenderers: {
// camera: (props) => customCameraComponent(props),
// preview: (props) => customPreviewComponent(props),
}
});

🎯 Integration Examples

Example 1: Material UI Application

// Material UI App with Nafsi SDK
import React from 'react';
import { ThemeProvider, createTheme, Box, Button } from '@mui/material';

const theme = createTheme({
palette: {
primary: { main: '#1976d2' },
secondary: { main: '#dc004e' }
},
typography: {
fontFamily: 'Roboto, sans-serif'
}
});

function App() {
const startVerification = async () => {
await window.Nafsi.init({
token: 'YOUR_JWT_TOKEN',
container: '#verification-container',
styleMode: 'headless',
styles: '/nafsi-mui-custom.css',
debug: true,
onSuccess: (result) => {
console.log('Verification successful!', result);
}
});

window.Nafsi.start();
};

return (
<ThemeProvider theme={theme}>
<Box sx={{ padding: 4 }}>
<Button variant="contained" onClick={startVerification}>
Start Verification
</Button>
<Box id="verification-container" sx={{ marginTop: 4 }} />
</Box>
</ThemeProvider>
);
}

Custom CSS file (nafsi-mui-custom.css):

/* Nafsi SDK custom styles for Material UI */
.nafsi-container {
font-family: var(--mui-typography-fontFamily, 'Roboto', sans-serif);
}

.nafsi-btn-primary {
background: var(--mui-palette-primary-main, #1976d2);
color: white;
border-radius: 4px;
padding: 10px 24px;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.5px;
}

.nafsi-btn-primary:hover {
background: var(--mui-palette-primary-dark, #115293);
}

.nafsi-camera-title {
color: var(--mui-palette-text-primary, #1a1a1a);
font-weight: 500;
}

Example 2: Ant Design Application

// Ant Design App with Nafsi SDK
import React from 'react';
import { ConfigProvider, Button } from 'antd';

const antTheme = {
token: {
colorPrimary: '#1890ff',
colorSuccess: '#52c41a',
fontFamily: 'Inter, sans-serif'
}
};

function App() {
const startVerification = async () => {
await window.Nafsi.init({
token: 'YOUR_JWT_TOKEN',
container: '#verification-container',
styleMode: 'headless',
styles: '/nafsi-antd-custom.css',
debug: true,
onSuccess: (result) => {
console.log('Verification successful!', result);
}
});

window.Nafsi.start();
};

return (
<ConfigProvider theme={antTheme}>
<div style={{ padding: 24 }}>
<Button type="primary" onClick={startVerification}>
Start Verification
</Button>
<div id="verification-container" style={{ marginTop: 24 }} />
</div>
</ConfigProvider>
);
}

Example 3: Vanilla JavaScript + Bootstrap

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Verification - Bootstrap</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<style>
:root {
--custom-primary: #0d6efd;
--custom-secondary: #6c757d;
--custom-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto;
}
</style>
</head>
<body>
<div class="container mt-5">
<button class="btn btn-primary" onclick="startVerification()">
Start Verification
</button>
<div id="verification-container" class="mt-4"></div>
</div>

<script src="https://sdk.nafsi.ai/v2/nafsi.js"></script>
<script>
async function startVerification() {
await Nafsi.init({
token: 'YOUR_JWT_TOKEN',
container: '#verification-container',
styleMode: 'inherit', // Inherits Bootstrap variables
debug: true
});

Nafsi.start();
}
</script>
</body>
</html>

Example 4: Custom Design System

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Custom Design Verification</title>
<style>
/* Your custom design system */
:root {
--custom-primary: #f97316;
--custom-secondary: #ea580c;
--custom-text: #1a1a1a;
--custom-background: #ffffff;
--custom-font-family: 'Inter', -apple-system, sans-serif;
}

body {
font-family: var(--custom-font-family);
color: var(--custom-text);
background: var(--custom-background);
margin: 0;
padding: 20px;
}

.verification-wrapper {
max-width: 800px;
margin: 0 auto;
padding: 40px;
background: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}

.start-btn {
background: var(--custom-primary);
color: white;
padding: 12px 32px;
border: none;
border-radius: 8px;
font-weight: 600;
cursor: pointer;
transition: all 0.2s;
}

.start-btn:hover {
background: var(--custom-secondary);
transform: translateY(-2px);
}
</style>
</head>
<body>
<div class="verification-wrapper">
<h1>Identity Verification</h1>
<p>Please verify your identity to continue</p>
<button class="start-btn" onclick="startVerification()">
Start Verification
</button>
<div id="verification-container" style="margin-top: 30px;"></div>
</div>

<script src="https://sdk.nafsi.ai/v2/nafsi.js"></script>
<script>
async function startVerification() {
await Nafsi.init({
token: 'YOUR_JWT_TOKEN',
container: '#verification-container',
styleMode: 'headless',
styles: './nafsi-custom.css', // Your custom styles
debug: true,
customization: {
theme: {
primaryColor: '#f97316',
secondaryColor: '#ea580c',
fontFamily: 'Inter, sans-serif'
}
},
onSuccess: (result) => {
alert('Verification successful!');
console.log(result);
},
onFailure: (error) => {
alert('Verification failed: ' + error.message);
}
});

Nafsi.start();
}
</script>
</body>
</html>

🎨 Creating Custom Styles

When using styleMode: 'headless', you have complete control over the SDK's appearance. Here's a comprehensive custom CSS template:

nafsi-custom.css Template:

/**
* Nafsi SDK - Custom Styles Template
* Use this as a starting point for your custom design
*/

/* ============================================
CONTAINER
============================================ */

.nafsi-container {
width: 100%;
font-family: var(--custom-font-family, 'Inter', sans-serif);
color: var(--custom-text, #1a1a1a);
background: transparent;
}

.nafsi-embedded {
position: relative;
width: 100%;
height: 100%;
min-height: 400px;
background: transparent;
}

/* ============================================
CAMERA VIEW
============================================ */

.nafsi-camera-container {
width: 100%;
max-width: 600px;
margin: 0 auto;
padding: 1rem;
}

.nafsi-camera-title {
font-size: 20px;
font-weight: 600;
color: var(--custom-text, #1a1a1a);
margin: 0 0 16px 0;
text-align: center;
}

.nafsi-camera-view {
position: relative;
width: 100%;
aspect-ratio: 16 / 9;
background: #000;
border-radius: 8px;
overflow: hidden;
margin: 0 auto 1rem;
}

.nafsi-camera-view.nafsi-camera-selfie {
aspect-ratio: 3 / 4; /* Portrait for selfie */
}

.nafsi-camera-video {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
object-fit: cover;
}

/* ============================================
OVERLAYS
============================================ */

.nafsi-camera-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 10;
}

/* ID card frame overlay */
.nafsi-frame-overlay {
position: absolute;
border: 3px solid #22c55e;
border-radius: 12px;
box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.5);
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 85%;
max-width: 420px;
aspect-ratio: 1.586 / 1;
}

/* Selfie face outline overlay */
.nafsi-selfie-overlay {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 80%;
max-width: 380px;
height: auto;
pointer-events: none;
z-index: 10;
}

.nafsi-selfie-overlay svg {
width: 100%;
height: auto;
display: block;
}

/* ============================================
BUTTONS
============================================ */

.nafsi-btn {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
padding: 12px 24px;
font-size: 16px;
font-weight: 600;
border: none;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
font-family: var(--custom-font-family, 'Inter', sans-serif);
}

.nafsi-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
}

.nafsi-btn-primary {
background: var(--custom-primary, #f97316);
color: white;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.nafsi-btn-primary:hover:not(:disabled) {
background: var(--custom-secondary, #ea580c);
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}

/* Centered button row with back arrow */
.nafsi-button-row-centered {
position: relative;
display: flex;
justify-content: center;
align-items: center;
}

.nafsi-btn-back-arrow {
position: absolute;
left: 0;
width: 48px;
height: 48px;
border-radius: 50%;
padding: 0;
background: transparent;
border: none;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s ease;
color: var(--custom-text, #1a1a1a);
}

.nafsi-btn-back-arrow:hover:not(:disabled) {
background: rgba(0, 0, 0, 0.05);
transform: scale(1.1);
}

/* Circular capture button */
.nafsi-btn-capture {
width: 72px;
height: 72px;
border-radius: 50%;
padding: 0;
background: transparent;
border: none;
cursor: pointer;
transition: all 0.2s ease;
}

.nafsi-btn-capture:disabled {
opacity: 0.4;
cursor: not-allowed;
}

.nafsi-btn-capture:hover:not(:disabled) {
transform: scale(1.08);
}

/* ============================================
CONFIRMATION PAGE
============================================ */

.nafsi-confirmation-container {
width: 100%;
max-width: 600px;
margin: 0 auto;
padding: 1rem;
}

.nafsi-confirmation-title {
font-size: 20px;
font-weight: 600;
color: var(--custom-text, #1a1a1a);
margin: 0 0 24px 0;
text-align: center;
}

.nafsi-confirmation-id-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
margin-bottom: 16px;
}

.nafsi-confirmation-card {
background: rgba(0, 0, 0, 0.02);
border: 1px solid rgba(0, 0, 0, 0.08);
border-radius: 8px;
overflow: hidden;
cursor: pointer;
transition: all 0.2s ease;
}

.nafsi-confirmation-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
border-color: var(--custom-primary, #f97316);
}

.nafsi-confirmation-card-label {
padding: 8px 12px;
font-size: 13px;
font-weight: 600;
color: var(--custom-text, #1a1a1a);
background: rgba(0, 0, 0, 0.03);
border-bottom: 1px solid rgba(0, 0, 0, 0.08);
}

.nafsi-confirmation-card-image {
width: 100%;
height: 140px;
object-fit: contain;
background: white;
padding: 8px;
}

/* ============================================
LOADING STATES
============================================ */

.nafsi-loading {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 20px;
padding: 40px;
}

.nafsi-spinner {
width: 48px;
height: 48px;
border: 4px solid rgba(0, 0, 0, 0.1);
border-top-color: var(--custom-primary, #f97316);
border-radius: 50%;
animation: spin 0.8s linear infinite;
}

@keyframes spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}

.nafsi-loading-text {
font-size: 18px;
font-weight: 500;
color: var(--custom-text, #1a1a1a);
}

/* ============================================
RESPONSIVE
============================================ */

@media (max-width: 768px) {
.nafsi-camera-container,
.nafsi-confirmation-container {
padding: 0.5rem;
}

.nafsi-confirmation-card-image {
height: 120px;
}
}

📚 API Methods

Nafsi.init(options)

Initialize the SDK with configuration. Returns a Promise.

await Nafsi.init({ token: 'YOUR_TOKEN' });

Nafsi.start()

Start the verification flow. Opens the UI.

Nafsi.start();

Nafsi.stop()

Stop and cleanup the verification flow.

Nafsi.stop();

Nafsi.getVersion()

Get SDK version string.

const version = Nafsi.getVersion();
console.log(version); // "2.0.0"

Nafsi.getConfig()

Get current configuration (sanitized, no sensitive data).

const config = Nafsi.getConfig();
console.log(config);
// { workflowId: "...", clientId: "...", version: "2.0.0" }

Nafsi.on(event, callback)

Register event listener. Returns unsubscribe function.

const unsubscribe = Nafsi.on('capture', (data) => {
console.log('Image captured:', data);
});

// Later: unsubscribe()

Nafsi.once(event, callback)

Register one-time event listener.

Nafsi.once('success', (result) => {
console.log('First success:', result);
});

Nafsi.off(event, callback)

Remove event listener.

Nafsi.off('capture', myCallback);

🔄 Verification Flow

  1. Landing Page (optional, can be skipped)

    • Logo, hero image, title, tagline
    • "Start Verification" button
  2. Camera Permission Request

    • Browser prompts for camera access
    • Clear explanation of why permission is needed
  3. ID Front Capture

    • Camera view with green frame overlay
    • Capture button enabled when camera ready
    • Back arrow to return (if not first step)
  4. ID Front Preview

    • Shows captured image (cropped to frame)
    • "Retake" or "Continue" options
  5. ID Back Capture

    • Same as front capture
    • Back arrow returns to front capture
  6. ID Back Preview

    • Shows captured image
    • "Retake" or "Continue" options
  7. Selfie Capture (if workflow requires)

    • Portrait aspect ratio (3:4)
    • Face outline overlay guide
    • Camera switches to front-facing
  8. Selfie Preview (if captured)

    • Shows captured selfie
    • "Retake" or "Continue" options
  9. Confirmation Page

    • Side-by-side ID cards (clickable for preview)
    • Selfie card if captured
    • "Submit" button
  10. Processing

    • Loading spinner with progress text
    • Submitting to verification API
  11. Result

    • Success message with next steps
    • OR error message with retry options

📸 Image Quality Specifications

ID Cards

  • Dimensions: 1014×640 pixels (landscape)
  • Format: JPEG
  • Quality: 82%
  • Size: ~80-120KB per image
  • Aspect Ratio: 1.586:1 (standard ID card)
  • Processing: Cropped to green frame overlay, resized, compressed

Selfie

  • Dimensions: 590×372 pixels (portrait)
  • Format: JPEG
  • Quality: 80%
  • Size: ~60-100KB
  • Aspect Ratio: 3:4 (portrait)
  • Processing: Face-centered, resized, compressed

Output Format

All images are returned as base64 data URLs:

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

🔐 JWT Token Structure

The JWT token must contain:

{
"workflowId": "workflow_abc123",
"clientId": "client_xyz789",
"config": "ke_national_id",
"apiUrl": "https://apisv2.windeal.co.ke/postdata",
"organisationId": "org_123",
"refresh_token": "refresh_token_here",
"iat": 1704355200,
"exp": 1704441600
}

Token generation should happen on your backend for security:

// Backend (Node.js example)
const jwt = require('jsonwebtoken');

const token = jwt.sign({
workflowId: 'workflow_abc123',
clientId: 'client_xyz789',
config: 'ke_national_id',
apiUrl: process.env.NAFSI_API_URL,
organisationId: 'org_123',
refresh_token: generatedRefreshToken,
}, process.env.JWT_SECRET, {
expiresIn: '24h'
});

// Send token to frontend
res.json({ token });

📦 Build Artifacts

When you commit and build, the following artifacts are created:

1. dist/nafsi.js

Main SDK bundle (minified, production-ready)

2. dist/nafsi.js.map

Source map for debugging

3. cdn-server/public/v2/nafsi.js

CDN-ready bundle (copied from dist)

4. cdn-server/public/v2/assets/nafsi-updated.css

Compiled CSS bundle (all styles concatenated)

5. cdn-server/public/v2/standalone/mui/

Material UI standalone app example

  • landing.html - Full landing page with SDK
  • nafsi-custom.css - Material UI themed styles

6. cdn-server/public/v2/standalone/antd/

Ant Design standalone app example

  • landing.html - Full landing page with SDK
  • nafsi-custom.css - Ant Design themed styles

All artifacts are production-ready and can be deployed to your CDN or hosting.


🚢 Deployment

Build Command

npm run build

This creates all artifacts in:

  • dist/ - SDK bundle
  • cdn-server/public/v2/ - CDN-ready files

Deploy to CDN

Upload these files to your CDN:

cdn-server/public/v2/
├── nafsi.js # Main SDK bundle
├── nafsi.js.map # Source map
├── assets/
│ └── nafsi-updated.css # CSS bundle
└── standalone/
├── mui/ # Material UI example
└── antd/ # Ant Design example

CDN URLs Structure

https://sdk.nafsi.ai/v2/nafsi.js
https://sdk.nafsi.ai/v2/assets/nafsi-updated.css
https://sdk.nafsi.ai/v2/standalone/mui/landing.html
https://sdk.nafsi.ai/v2/standalone/antd/landing.html

🧪 Testing

Local Development Server

npm run dev

This starts:

  • Rollup watch mode (auto-rebuild on changes)
  • CDN server on port 4413

Test Page

Open test/test.html in your browser:

open test/test.html

Or start a simple HTTP server:

npx http-server . -p 8080

Then visit: http://localhost:8080/test/test.html


🌐 Browser Support

  • Chrome 60+
  • Firefox 60+
  • Safari 12+
  • Edge 79+
  • Opera 50+
  • Samsung Internet 8+

Required Browser Features

  • WebRTC (getUserMedia for camera access)
  • Canvas API (for image processing)
  • Fetch API (for network requests)
  • localStorage (for token management)
  • CSS Custom Properties (for theming)

🐛 Troubleshooting

Camera not working

Problem: Camera permission denied or not accessible

Solutions:

  • Ensure HTTPS (camera requires secure context)
  • Check browser permissions in Settings
  • Verify camera is not in use by another application
  • Try a different browser
  • Check console for specific error messages

Token expired errors

Problem: JWT token expired during verification

Solutions:

  • Ensure autoRefresh: true is set (default)
  • Verify refresh_token is included in JWT payload
  • Check token expiry time is reasonable (24h recommended)
  • Implement onTokenRefresh callback to save new tokens

Styles not inheriting

Problem: SDK doesn't match your app's design

Solutions:

  • Ensure CSS variables are defined at :root level
  • Use styleMode: 'headless' with custom CSS file
  • Check browser console for CSS variable detection logs (enable debug: true)
  • Verify framework-specific variable names (e.g., --mui-palette-primary-main)

Build errors

Problem: Build fails with errors

Solutions:

  • Run npm install to ensure dependencies are installed
  • Check Node.js version (16+ recommended)
  • Clear node_modules and reinstall: rm -rf node_modules && npm install
  • Check for syntax errors in source files

Images not uploading

Problem: Verification fails at submission

Solutions:

  • Check network tab for API request/response
  • Verify JWT token has correct apiUrl
  • Ensure images are within size limits (~150KB total)
  • Enable debug: true to see detailed logs

📝 License

MIT License - Copyright (c) 2024 Nafsi Team


🤝 Support


🎉 What's New in v2.0.0

  • Design-Agnostic Architecture - Works with any design system
  • Framework Detection - Auto-detects Material UI, Ant Design, Bootstrap
  • Embedded Mode - Renders inline, not just fullscreen
  • Three Style Modes - inherit, headless, standalone
  • Improved Image Quality - Professional-grade capture pipeline
  • Selfie Face Overlay - Visual guide for selfie capture
  • Workflow Auto-Fetch - SDK fetches workflow configuration automatically
  • Better Token Management - Automatic refresh with localStorage
  • Comprehensive Callbacks - onStepChange, onCapture, onTokenRefresh
  • Mobile-Optimized - Perfect responsive behavior
  • Developer-Friendly - Extensive documentation and examples

Ready to integrate? Start with the Quick Start section above!