kyvshield_lite

KyvShield Lite — Flutter KYC SDK powered by WebView. Same API as the native kyvshield SDK, zero native ML dependencies.

pub package

Ideal for MVPs and cross-platform apps that want identity verification without heavy native camera/ML packages.

How it works

  1. Kyvshield.initKyc() opens a full-screen WebView.
  2. The WebView loads the KyvShield Web SDK from your API server.
  3. The entire KYC flow runs inside the WebView (camera, liveness, OCR, face match).
  4. The result is sent back to Flutter via a JS bridge and parsed into typed Dart objects.

Installation

dependencies:
  kyvshield_lite: ^0.0.3

Full Example

All possible enum values listed. Code is copy-paste ready.

import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:kyvshield_lite/kyvshield_lite.dart';

// ── Possible values ───────────────────────────────────────────────────
// CaptureStep        : selfie, recto, verso
// ChallengeMode      : minimal, standard, strict
// SelfieDisplayMode  : standard, compact, immersive, neonHud
// DocumentDisplayMode: standard, compact, immersive, neonHud
// ChallengeAudioRepeat: once, twice, thrice
// Brightness         : light, dark

// ── Fetch available documents from API ────────────────────────────────
// See https://kyvshield.innolink.sn/developer for full API docs

final res = await http.get(
  Uri.parse('https://kyvshield.innolink.sn/api/v1/documents'),
  headers: {'X-API-Key': 'YOUR_API_KEY'},
);
final docs = (jsonDecode(res.body)['documents'] as List)
    .map((d) => KyvshieldDocument.fromJson(d))
    .toList();
final selectedDoc = docs.first; // or let user pick

// ── Request camera permission ─────────────────────────────────────────

final granted = await Kyvshield.requestCameraPermission();
if (!granted) {
  final denied = await Kyvshield.isCameraPermissionPermanentlyDenied();
  if (denied) await Kyvshield.openSettings();
  return;
}

// ── Start KYC ─────────────────────────────────────────────────────────

final result = await Kyvshield.initKyc(
  context: context,
  config: KyvshieldConfig(
    baseUrl: 'https://kyvshield.innolink.sn',
    apiKey: 'YOUR_API_KEY',
    enableLog: true,
    theme: KyvshieldThemeConfig(
      primaryColor: Color(0xFFEF8352),
      brightness: Brightness.light,
    ),
  ),
  flow: KyvshieldFlowConfig(
    steps: [CaptureStep.selfie, CaptureStep.recto, CaptureStep.verso],
    language: 'fr',
    showIntroPage: true,
    showInstructionPages: true,
    showResultPage: true,
    showSuccessPerStep: true,
    selfieDisplayMode: SelfieDisplayMode.standard,
    documentDisplayMode: DocumentDisplayMode.standard,
    challengeMode: ChallengeMode.minimal,
    requireFaceMatch: true,
    playChallengeAudio: true,
    maxChallengeAudioPlay: ChallengeAudioRepeat.once,
    pauseBetweenAudioPlay: Duration(seconds: 1),
    stepChallengeModes: {
      CaptureStep.selfie: ChallengeMode.minimal,
      CaptureStep.recto: ChallengeMode.standard,
      CaptureStep.verso: ChallengeMode.minimal,
    },
    target: selectedDoc,
    kycIdentifier: 'user-12345',
  ),
);

// ── Start KYC ─────────────────────────────────────────────────────────

final result = await Kyvshield.initKyc(
  context: context,
  config: config,
  flow: flow,
);

// ── Handle result ─────────────────────────────────────────────────────

print('Success: ${result.success}');
print('Status: ${result.overallStatus}');   // pass, review, reject, error
print('Session: ${result.sessionId}');

// Selfie
if (result.selfieResult != null) {
  print('Live: ${result.selfieResult!.isLive}');
  print('Image: ${result.selfieResult!.capturedImage?.length} bytes');
}

// Recto
if (result.rectoResult != null) {
  print('Recto score: ${result.rectoResult!.score}');
  print('Aligned doc: ${result.rectoResult!.alignedDocument?.length} bytes');
  print('Photos: ${result.rectoResult!.extractedPhotos.length}');
  print('Fields: ${result.rectoResult!.extraction?.fields.length}');
  // Face match (attached to recto)
  if (result.rectoResult!.faceVerification != null) {
    print('Face match: ${result.rectoResult!.faceVerification!.isMatch}');
    print('Similarity: ${result.rectoResult!.faceVerification!.similarityScore}');
  }
}

// Verso
if (result.versoResult != null) {
  print('Verso score: ${result.versoResult!.score}');
  print('Fields: ${result.versoResult!.extraction?.fields.length}');
}

// Extracted data (searches recto + verso)
print('Nom: ${result.getExtractedValue("nom")}');
print('NIN: ${result.getExtractedValue("nin")}');

// Loop all fields sorted by priority
for (final field in result.rectoResult?.extraction?.sortedFields ?? []) {
  print('${field.label}: ${field.stringValue}');
}

Configuration

KyvshieldConfig

KyvshieldConfig(
  baseUrl: 'https://kyvshield.innolink.sn',  // API server URL
  apiKey: 'your-api-key',                     // API key from dashboard
  apiVersion: 'v1',                            // API version (default: v1)
  enableLog: true,                             // Debug logs (default: false)
  timeoutSeconds: 60,                          // Request timeout
  theme: KyvshieldThemeConfig(
    primaryColor: Color(0xFFEF8352),           // Brand color
    successColor: Colors.green,
    warningColor: Colors.orange,
    errorColor: Colors.red,
    brightness: Brightness.light,              // Light or dark mode
  ),
)

KyvshieldFlowConfig

KyvshieldFlowConfig(
  // Steps to perform
  steps: [CaptureStep.selfie, CaptureStep.recto, CaptureStep.verso],

  // Language ('fr', 'en', 'wo')
  language: 'fr',

  // UI pages
  showIntroPage: true,           // Welcome screen
  showInstructionPages: true,    // Per-step instruction pages
  showResultPage: true,          // Final result summary
  showSuccessPerStep: true,      // Success animation after each step

  // Display modes
  selfieDisplayMode: SelfieDisplayMode.standard,     // standard, compact, immersive, neonHud
  documentDisplayMode: DocumentDisplayMode.standard,  // standard, compact, immersive, neonHud

  // Verification options
  challengeMode: ChallengeMode.normal,  // easy, normal, hard
  requireFaceMatch: true,               // Selfie vs document face comparison

  // Audio
  playChallengeAudio: true,
  maxChallengeAudioPlay: ChallengeAudioRepeat.once,  // once, twice, thrice
  pauseBetweenAudioPlay: Duration(seconds: 1),

  // Document target
  target: KyvshieldDocument(
    docType: 'SN-CIN',
    name: "Carte Nationale d'Identite",
    category: 'identity_card',
    country: 'SN',
    countryName: 'Senegal',
  ),

  // Optional: per-step challenge modes
  stepChallengeModes: {
    CaptureStep.selfie: ChallengeMode.hard,
    CaptureStep.recto: ChallengeMode.normal,
  },

  // Optional: webhook correlation ID
  kycIdentifier: 'user-12345',
)

Display Modes

Mode Description
standard Classic layout with header, camera, and instructions below
compact Camera fills screen, instructions overlay at bottom
immersive Full-screen camera with glass-effect overlays
neonHud Futuristic dark theme with glow effects and monospace font

Result Structure

final result = await Kyvshield.initKyc(...);

// Overall result
result.success;              // bool
result.overallStatus;        // VerificationStatus (pass, review, reject, error)
result.sessionId;            // String?
result.errorMessage;         // String? (if error)
result.totalProcessingTimeMs; // int

// Selfie result
result.selfieResult?.isLive;           // bool
result.selfieResult?.confidence;       // double
result.selfieResult?.capturedImage;    // Uint8List? (JPEG bytes)

// Document results (recto / verso)
result.rectoResult?.status;                          // VerificationStatus
result.rectoResult?.alignedDocument;                 // Uint8List? (aligned JPEG)
result.rectoResult?.extraction?.fields;              // List<ExtractedField>
result.rectoResult?.extraction?.sortedFields;        // Sorted by displayPriority
result.rectoResult?.extractedPhotos;                 // List<ExtractedPhoto>
result.rectoResult?.faceVerification?.isMatch;       // bool?
result.rectoResult?.faceVerification?.similarityScore; // double?

// Convenience getters
result.getExtractedValue('nom');       // Search across recto + verso
result.mainPhoto;                      // First extracted face photo
result.faceMatches;                    // Face match boolean
result.selfieImage;                    // Uint8List?
result.rectoImage;                     // Uint8List?
result.versoImage;                     // Uint8List?
result.authenticityScore;              // Average document score

ExtractedField

Each OCR field has rich metadata for display:

for (final field in result.rectoResult?.extraction?.sortedFields ?? []) {
  print('${field.label}: ${field.stringValue}');
  // field.key           — generic key (e.g. "first_name")
  // field.documentKey   — document-specific key (e.g. "prenoms")
  // field.label         — localized label (e.g. "Prénoms")
  // field.value         — extracted value
  // field.displayPriority — sort order (lower = first)
  // field.icon          — icon name (e.g. "user", "calendar")
}

Permission Helpers

await Kyvshield.checkCameraPermission();                // bool
await Kyvshield.requestCameraPermission();              // bool
await Kyvshield.isCameraPermissionPermanentlyDenied();  // bool
await Kyvshield.openSettings();                         // Opens app settings

Platform Setup

Android

android/app/src/main/AndroidManifest.xml:

<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />

android/app/build.gradle — minimum SDK 21:

minSdkVersion 21

iOS

ios/Runner/Info.plist:

<key>NSCameraUsageDescription</key>
<string>Camera access is required for identity verification.</string>

API Documentation

Full documentation and developer dashboard: https://kyvshield.innolink.sn/developer

License

BSD 3-Clause License. See LICENSE.

Libraries

kyvshield_lite
KyvShield Lite — WebView-based KYC SDK for Flutter.