KyvShield SDK

Integrez la verification d'identite KYC complete dans votre application en quelques minutes.

Integration avec IA

Utilisez Claude Code pour integrer KyvShield automatiquement dans votre projet. Le plugin donne a Claude toute la documentation, les exemples et les types SDK.

Installez le plugin dans Claude Code :

/plugin marketplace add moussa-innolink/kyv_llm_skill
/plugin install kyvshield@kyvshield-plugins
/reload-plugins

Puis demandez a Claude :

"Integre KyvShield dans mon app Flutter"
"Add KYC verification to my Android app"
"Help me handle KyvShield webhooks in Node.js"

Voir le plugin sur GitHub — Fonctionne avec Claude Code

Flow KYC Complet

Document
Capture recto/verso
Liveness
Detection anti-spoof
Face Match
Selfie vs document
Resultat
PASS / REVIEW / REJECT

Installation

Choisissez votre plateforme

# pubspec.yaml
dependencies:
kyvshield_lite:
git:
url: https://github.com/moussa-innolink/kyv_flutter_lite.git
ref: 0.0.4
flutter pub get

Source: GitHub

<!-- CDN -->
<script src="https://kyvshield-naruto.innolinkcloud.com/static/sdk/kyvshield.min.js"></script>
iOS Safari : L'audio des challenges nécessite une interaction utilisateur (AudioContext suspendu). Le SDK déverrouille l'AudioContext automatiquement lors du clic « Lancer KYC » en jouant un son réel dans le handler du geste. Testez sur un vrai iPhone — le simulateur iOS est plus restrictif.
HTTPS requis : La caméra (getUserMedia) ne fonctionne que sur https:// ou localhost. En production, servez toujours votre page en HTTPS.
// settings.gradle.kts
dependencyResolutionManagement {
repositories {
maven { url = uri("https://jitpack.io") }
}
}
// app/build.gradle.kts
dependencies {
implementation("com.github.moussa-innolink:kyv_android:0.0.4")
}

Min SDK 21. Source: JitPackGitHub

Swift Package Manager

// Xcode → File → Add Package Dependencies
URL: https://github.com/moussa-innolink/kyv_swift.git
Version: 0.0.4
// Package.swift
.package(url: "https://github.com/moussa-innolink/kyv_swift.git", exact: "0.0.4")
iOS : Ajoutez NSCameraUsageDescription dans votre Info.plist.

Source: GitHub

npm install @kyvshield/react-native-lite@0.0.4
# Peer dependencies
npm install react-native-webview react-native-permissions
# iOS
cd ios && pod install
iOS : Ajoutez NSCameraUsageDescription dans votre Info.plist.

Source: npmGitHub

Exemple complet

Integration complete par plateforme

URLs des environnements
Developpement
https://kyvshield.innolink.sn
Production
https://kyvshield-naruto.innolinkcloud.com

Utilisez l'URL de developpement pour les tests, puis remplacez par l'URL de production pour le deploiement.

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

final res = await http.get(
  Uri.parse('https://kyvshield-naruto.innolinkcloud.com/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-naruto.innolinkcloud.com',
    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',
  ),
);

// ── 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}');
}
<!-- Include SDK -->
<script src="https://kyvshield-naruto.innolinkcloud.com/static/sdk/kyvshield.min.js"></script>

<script>
// ── Possible values ───────────────────────────────────────────────────
// steps       : 'selfie', 'recto', 'verso'
// challengeMode: 'minimal', 'standard', 'strict'
// selfieDisplayMode / documentDisplayMode: 'standard', 'compact', 'immersive', 'neonHud'
// themeMode   : 'light', 'dark'

// ── Fetch documents from API ──────────────────────────────────────────
const res = await fetch('https://kyvshield-naruto.innolinkcloud.com/api/v1/documents', {
  headers: { 'X-API-Key': 'YOUR_API_KEY' },
});
const { documents } = await res.json();
const selectedDoc = documents[0]; // or let user pick

// ── Start KYC ─────────────────────────────────────────────────────────
const result = await KyvShield.initKyc(
  {
    baseUrl: 'https://kyvshield-naruto.innolinkcloud.com',
    apiKey: 'YOUR_API_KEY',
    enableLog: true,
    theme: { primaryColor: '#EF8352', themeMode: 'light' },
  },
  {
    steps: ['selfie', 'recto', 'verso'],
    language: 'fr',
    showIntroPage: true,
    showInstructionPages: true,
    showResultPage: true,
    showSuccessPerStep: true,
    selfieDisplayMode: 'standard',
    documentDisplayMode: 'standard',
    challengeMode: 'minimal',
    requireFaceMatch: true,
    playChallengeAudio: true,
    maxChallengeAudioPlay: 1,
    pauseBetweenAudioPlay: 1000,
    target: selectedDoc,
    kycIdentifier: 'user-12345',
  }
);

// ── Handle result ─────────────────────────────────────────────────────
console.log('Success:', result.success);
console.log('Status:', result.overallStatus);   // pass, reject, error
console.log('Session:', result.sessionId);

// Selfie
if (result.selfieResult) {
  console.log('Live:', result.selfieResult.isLive);
  console.log('Image:', result.selfieResult.capturedImage?.length, 'bytes');
}

// Recto
if (result.rectoResult) {
  console.log('Score:', result.rectoResult.score);
  console.log('Fields:', result.rectoResult.extraction?.fields?.length);
  if (result.rectoResult.faceVerification) {
    console.log('Face match:', result.rectoResult.faceVerification.isMatch);
    console.log('Similarity:', result.rectoResult.faceVerification.similarityScore);
  }
}

// Extracted data
console.log('Nom:', result.getExtractedValue?.('nom'));
console.log('NIN:', result.getExtractedValue?.('nin'));
</script>
import sn.innolink.kyvshield.lite.KyvshieldLite
import sn.innolink.kyvshield.lite.KyvshieldInput
import sn.innolink.kyvshield.lite.config.*
import sn.innolink.kyvshield.lite.result.*

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

// ── Fetch documents from API ──────────────────────────────────────────
// GET https://kyvshield-naruto.innolinkcloud.com/api/v1/documents
// Header: X-API-Key: YOUR_API_KEY
// Parse response -> List<KyvshieldDocument> via KyvshieldDocument.fromJson()

// ── With Jetpack Compose (ActivityResultContract) ─────────────────────

val kycLauncher = rememberLauncherForActivityResult(
    KyvshieldLite.getResultContract()
) { result: KYCResult ->
    // Handle result
    println("Success: ${result.success}")
    println("Status: ${result.overallStatus}")
}

// Build config
val config = KyvshieldConfig(
    baseUrl = "https://kyvshield-naruto.innolinkcloud.com",
    apiKey = "YOUR_API_KEY",
    enableLog = true,
    theme = KyvshieldThemeConfig(
        primaryColor = 0xFFEF8352.toInt(),
        darkMode = false,
    ),
)

// Build flow
val flow = KyvshieldFlowConfig(
    steps = listOf(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,
    pauseBetweenAudioPlayMs = 1000L,
    stepChallengeModes = mapOf(
        CaptureStep.selfie to ChallengeMode.minimal,
        CaptureStep.recto to ChallengeMode.standard,
        CaptureStep.verso to ChallengeMode.minimal,
    ),
    target = selectedDoc,
    kycIdentifier = "user-12345",
)

// Launch KYC
kycLauncher.launch(KyvshieldInput(config, flow))

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

println("Success: ${result.success}")
println("Status: ${result.overallStatus}")   // pass, reject, error
println("Session: ${result.sessionId}")

// Selfie
result.selfieResult?.let {
    println("Live: ${it.isLive}")
    println("Image: ${it.capturedImage?.size ?: 0} bytes")
}

// Recto
result.rectoResult?.let {
    println("Recto score: ${it.score}")
    println("Aligned doc: ${it.alignedDocument?.size ?: 0} bytes")
    println("Photos: ${it.extractedPhotos.size}")
    println("Fields: ${it.extraction?.fields?.size ?: 0}")
    it.faceVerification?.let { fv ->
        println("Face match: ${fv.isMatch}")
        println("Similarity: ${fv.similarityScore}")
    }
}

// Verso
result.versoResult?.let {
    println("Verso score: ${it.score}")
    println("Fields: ${it.extraction?.fields?.size ?: 0}")
}

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

// Loop all fields sorted by priority
result.rectoResult?.extraction?.sortedFields?.forEach { field ->
    println("${field.label}: ${field.stringValue}")
}
import KyvshieldLite

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

// ── Fetch documents from API ──────────────────────────────────────────

let url = URL(string: "https://kyvshield-naruto.innolinkcloud.com/api/v1/documents")!
var request = URLRequest(url: url)
request.setValue("YOUR_API_KEY", forHTTPHeaderField: "X-API-Key")
let (data, _) = try await URLSession.shared.data(for: request)
let json = try JSONSerialization.jsonObject(with: data) as! [String: Any]
let docsJson = json["documents"] as! [[String: Any]]
let docs = docsJson.map { KyvshieldDocument.fromJson($0) }
let selectedDoc = docs.first!

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

KyvshieldLite.requestCameraPermission { granted in
    guard granted else {
        if KyvshieldLite.isCameraPermissionDenied() {
            KyvshieldLite.openSettings()
        }
        return
    }
    startKyc(doc: selectedDoc)
}

// ── Start KYC (UIKit) ─────────────────────────────────────────────────

func startKyc(doc: KyvshieldDocument) {
    let config = KyvshieldConfig(
        baseUrl: "https://kyvshield-naruto.innolinkcloud.com",
        apiKey: "YOUR_API_KEY",
        enableLog: true,
        theme: KyvshieldThemeConfig(
            primaryColor: UIColor(hex: "#EF8352"),
            brightness: .light
        )
    )

    let flow = KyvshieldFlowConfig(
        steps: [.selfie, .recto, .verso],
        target: doc,
        kycIdentifier: "user-12345",
        challengeMode: .minimal,
        stepChallengeModes: [
            .selfie: .minimal,
            .recto: .standard,
            .verso: .minimal,
        ],
        selfieDisplayMode: .standard,
        documentDisplayMode: .standard,
        requireFaceMatch: true,
        showIntroPage: true,
        showInstructionPages: true,
        showResultPage: true,
        showSuccessPerStep: true,
        language: "fr",
        playChallengeAudio: true,
        maxChallengeAudioPlay: .once,
        pauseBetweenAudioPlayMs: 1000
    )

    KyvshieldLite.initKyc(from: self, config: config, flow: flow) { result in
        self.handleResult(result)
    }
}

// ── SwiftUI alternative ───────────────────────────────────────────────

// .fullScreenCover(isPresented: $showKyc) {
//     KyvshieldView(config: config, flow: flow) { result in
//         showKyc = false
//         handleResult(result)
//     }
// }

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

func handleResult(_ result: KYCResult) {
    print("Success: \(result.success)")
    print("Status: \(result.overallStatus)")   // .pass, .reject, .error
    print("Session: \(result.sessionId ?? "")")

    // Selfie
    if let selfie = result.selfieResult {
        print("Live: \(selfie.isLive)")
        print("Image: \(selfie.capturedImage?.count ?? 0) bytes")
    }

    // Recto
    if let recto = result.rectoResult {
        print("Recto score: \(recto.score)")
        print("Aligned doc: \(recto.alignedDocument?.count ?? 0) bytes")
        print("Photos: \(recto.extractedPhotos.count)")
        print("Fields: \(recto.extraction?.fields.count ?? 0)")
        if let fv = recto.faceVerification {
            print("Face match: \(fv.isMatch)")
            print("Similarity: \(fv.similarityScore)")
        }
    }

    // Verso
    if let verso = result.versoResult {
        print("Verso score: \(verso.score)")
        print("Fields: \(verso.extraction?.fields.count ?? 0)")
    }

    // Extracted data (searches recto + verso)
    print("Nom: \(result.getExtractedValue("nom") ?? "")")
    print("NIN: \(result.getExtractedValue("nin") ?? "")")

    // Loop all fields sorted by priority
    for field in result.rectoResult?.extraction?.sortedFields ?? [] {
        print("\(field.label): \(field.stringValue ?? "")")
    }
}
import React, { useState } from 'react';
import { Button, View, Alert } from 'react-native';
import {
  KyvshieldModal,
  FlowPresets,
  requestCameraPermission,
  isCameraPermissionDenied,
  openSettings,
  kycResultGetExtractedValue,
} from '@kyvshield/react-native';
import type {
  KyvshieldConfig,
  KyvshieldFlowConfig,
  KyvshieldDocument,
  CaptureStep,
  ChallengeMode,
  SelfieDisplayMode,
  DocumentDisplayMode,
  KYCResult,
} from '@kyvshield/react-native';

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

// ── Fetch documents from API ──────────────────────────────────────────

async function fetchDocuments(): Promise<KyvshieldDocument[]> {
  const res = await fetch(
    'https://kyvshield-naruto.innolinkcloud.com/api/v1/documents',
    { headers: { 'X-API-Key': 'YOUR_API_KEY' } },
  );
  const { documents } = await res.json();
  return documents;
}

// ── Config ────────────────────────────────────────────────────────────

const config: KyvshieldConfig = {
  baseUrl: 'https://kyvshield-naruto.innolinkcloud.com',
  apiKey: 'YOUR_API_KEY',
  enableLog: true,
  theme: {
    primaryColor: '#EF8352',
    darkMode: false,
  },
};

// ── Flow ──────────────────────────────────────────────────────────────

const flow: KyvshieldFlowConfig = {
  steps: ['selfie', 'recto', 'verso'] as CaptureStep[],
  language: 'fr',
  showIntroPage: true,
  showInstructionPages: true,
  showResultPage: true,
  showSuccessPerStep: true,
  selfieDisplayMode: 'standard' as SelfieDisplayMode,
  documentDisplayMode: 'standard' as DocumentDisplayMode,
  challengeMode: 'minimal' as ChallengeMode,
  requireFaceMatch: true,
  playChallengeAudio: true,
  maxChallengeAudioPlay: 'once',
  pauseBetweenAudioPlayMs: 1000,
  target: selectedDoc,
  kycIdentifier: 'user-12345',
};

// ── Component ─────────────────────────────────────────────────────────

export default function App() {
  const [visible, setVisible] = useState(false);

  const startKyc = async () => {
    const granted = await requestCameraPermission();
    if (!granted) {
      const denied = await isCameraPermissionDenied();
      if (denied) openSettings();
      return;
    }
    setVisible(true);
  };

  const handleResult = (result: KYCResult) => {
    setVisible(false);

    console.log('Success:', result.success);
    console.log('Status:', result.overallStatus); // pass, reject, error
    console.log('Session:', result.sessionId);

    // Selfie
    if (result.selfieResult) {
      console.log('Live:', result.selfieResult.isLive);
      console.log('Image:', result.selfieResult.capturedImageBase64?.length, 'chars');
    }

    // Recto
    if (result.rectoResult) {
      console.log('Score:', result.rectoResult.score);
      console.log('Fields:', result.rectoResult.extraction?.fields?.length);
      if (result.rectoResult.faceVerification) {
        console.log('Face match:', result.rectoResult.faceVerification.isMatch);
        console.log('Similarity:', result.rectoResult.faceVerification.similarityScore);
      }
    }

    // Extracted data
    console.log('Nom:', kycResultGetExtractedValue(result, 'nom'));
    console.log('NIN:', kycResultGetExtractedValue(result, 'nin'));
  };

  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      <Button title="Start KYC" onPress={startKyc} />
      <KyvshieldModal
        visible={visible}
        config={config}
        flow={flow}
        onResult={handleResult}
        onDismiss={() => setVisible(false)}
      />
    </View>
  );
}

API Reference

Common to all platforms

Methods

Method Returns Description
initKyc(config, flow)KycResultLaunch the full KYC flow. Returns result when user completes or cancels.
checkCameraPermission()boolCheck and request camera permission. Returns true if granted.
isCameraPermissionPermanentlyDenied()boolReturns true if camera was permanently denied. User must go to Settings.
openSettings()boolOpens device settings so user can manually enable camera permission.

config KyvshieldConfig

Property Type Default Description
baseUrlStringrequiredAPI server URL
apiKeyStringrequiredAPI key from dashboard
apiVersionString'v1'API version
timeoutSecondsint60HTTP request timeout (not session timeout)
enableLogboolfalseEnable debug logs
themeThemeConfig?nullCustom theme (see below)
acceptReviewStatusboolfalseAccept REVIEW status as valid

flow KyvshieldFlowConfig

Property Type Default Description
stepsCaptureStep[][selfie, recto, verso]Steps to execute (order matters)
targetString?null (auto-detect)Document type: SN-CIN, SN-PASSPORT, SN-DRIVER-LICENCE
challengeModeChallengeModestandardminimal (1) | standard (3) | strict (5+)
languageString'fr'fr | en | wo
showIntroPagebooltrueShow intro screen before KYC
showInstructionPagesbooltrueShow instructions before each step
showResultPagebooltrueShow result screen after KYC
showSuccessPerStepbooltrueShow success animation after each step
requireFaceMatchbooltrueCompare selfie vs document photo
selfieDisplayModeDisplayModestandardstandard | compact | immersive
documentDisplayModeDisplayModestandardstandard | compact | immersive
playChallengeAudioboolfalsePlay voice instructions for each challenge
maxChallengeAudioPlayint1Repeat audio 1, 2 or 3 times
kycIdentifierString?nullYour reference ID (returned in webhooks)
stringsStrings?nullOverride any UI text
Flow Presets (Flutter)
KyvshieldFlowConfig.selfieOnly()

Selfie only, no document

KyvshieldFlowConfig.standard()

Selfie + recto (recommended)

KyvshieldFlowConfig.full()

Selfie + recto + verso

KyvshieldFlowConfig.quick()

No UI pages, minimal challenges

KyvshieldFlowConfig.strict()

Maximum security (5+ challenges)

KyvshieldFlowConfig.documentOnly()

Recto + verso, no selfie

ThemeConfig

Property Type Description
primaryColorColor / StringBrand color (buttons, accents, indicators)
successColorColor?Success feedback color (default: #10B981)
warningColorColor?Warning color (default: #F59E0B)
errorColorColor?Error color (default: #EF4444)
themeModeStringlight | dark | auto
Presets
Innolink#EF8352
Blue#3B82F6
Green#10B981
Purple#8B5CF6
Kratos#00377D
Luna#FFD100

Localization & Custom Strings

3 built-in languages. Override any text via strings parameter.

frFrancais
enEnglish
woWolof
Customizable string keys
Intro Screen
  • introTitle
  • introSubtitle
  • introStartButton
  • introDocTypeLabel
  • introDocTypePrefix
Selfie Capture
  • selfieStepTitle
  • selfiePlaceFace
  • selfieHoldStill
  • selfieFaceDetected
  • selfieNoFace
  • selfieTooFar
  • selfieTooClose
  • selfieNotCentered
Document Capture
  • stepRectoTitle
  • stepVersoTitle
  • capturePlaceCardInFrame
  • captureDocumentDetected
  • captureLookingForDocument
Challenges
  • challengeCenterFace
  • challengeCloseEyes
  • challengeTurnLeft
  • challengeTurnRight
  • challengeSmile
  • challengeLookUp
  • challengeLookDown
Results
  • resultLivenessSuccess
  • resultLivenessFailed
  • resultScoreLabel
  • resultRetryPrompt
  • buttonContinue
  • buttonRetry
  • buttonCancel
Analysis
  • statusAnalyzing
  • analyzingPleaseWait
  • analysisStepImageCaptured
  • analysisStepQualityVerified
  • analysisStepSecurityCheck
  • analysisStepAIVerification

enum Types & Enums

CaptureStep
selfieFace liveness verification
rectoDocument front side (OCR + fraud check)
versoDocument back side (NIN + MRZ)
ChallengeMode
minimal1-2 challenges (fastest)
standard3-4 challenges (balanced, default)
strict5+ challenges (max security)
DisplayMode
standardCamera + instructions section below
compactCamera + overlaid instructions
immersiveFull-screen camera, floating overlays
Supported Documents
SN-CINSenegal ID Card (recto + verso)
SN-PASSPORTSenegal Passport (recto only)
SN-DRIVER-LICENCESenegal Driver License (recto + verso)

result KYCResult

Returned by initKyc() / startKyc()

Property Type Description
successboolKYC completed without error
overallStatusVerificationStatuspass | review | reject | error
sessionIdString?Server session ID (for webhook correlation)
selfieResultSelfieResult?Liveness verification result
rectoResultDocumentResult?Front side OCR + fraud analysis
versoResultDocumentResult?Back side (NIN, MRZ)
fraudIndicatorsString[]Detected fraud signals
totalProcessingTimeMsintTotal processing time
errorMessageString?Error description if failed
Convenience getters:
selfieImagebytes?Captured selfie image
rectoImagebytes?Aligned recto document image
versoImagebytes?Aligned verso document image
faceMatchesbool?Selfie matches document photo
faceSimilarityScoredouble?Face match score (0-100%)
getExtractedValue(key)String?Get any OCR field by key
mainPhotoExtractedPhoto?Face photo from document

result SelfieResult

PropertyTypeDescription
successboolStep completed successfully
isLiveboolReal person detected (not photo/screen/mask)
confidencedoubleConfidence score (0.0 - 1.0)
statusVerificationStatuspass or reject
capturedImagebytes?Captured selfie image (JPEG)
challengesPassedintNumber of challenges passed
challengesTotalintTotal challenges assigned
userMessagesString[]Localized messages for user
spoofingIndicatorsString[]Detected spoofing signals
processingTimeMsintProcessing time in ms

result DocumentResult

Used for both recto and verso

PropertyTypeDescription
successboolStep completed successfully
isLiveboolReal physical document (not screen/printout)
scoredoubleAuthenticity score (0.0 - 1.0)
confidenceLevelStringHIGH | MEDIUM | LOW
statusVerificationStatuspass | review | reject
alignedDocumentbytes?Perspective-corrected document image
extractionDocumentData?OCR extracted fields (see below)
extractedPhotosExtractedPhoto[]Face photos extracted from document
faceVerificationFaceResult?Selfie vs document face match
userMessagesString[]Localized messages for user
fraudIndicatorsString[]Detected fraud signals
processingTimeMsintProcessing time in ms

data ExtractedField & DocumentData

DocumentData contains a sorted list of ExtractedField. Use getValue(key) or iterate sortedFields.

PropertyTypeDescription
keyStringGeneric key (document_id, first_name, birth_date...)
documentKeyStringDocument-specific key (numero_carte, prenoms...)
labelStringLocalized display label
valueanyExtracted value (String, array, or map)
displayPriorityintSort order (lower = show first)
iconString?Icon name (user, calendar, credit_card...)

Convention displayPriority

  • displayPriority = 1 → Toujours l'identifiant principal du document (N° carte pour le recto, NIN pour le verso)
  • displayPriority = 2-10 → Champs importants (nom, prénom, date de naissance...)
  • displayPriority = 99+ → Champs techniques (MRZ, codes internes)

Utilisez sortedFields pour afficher les champs dans l'ordre recommandé.

DocumentData methods

sortedFields → fields sorted by displayPriority (identifiant en premier)
getField(key) → ExtractedField? (by generic or document key)
getValue(key) → String? (value as string)

data ExtractedPhoto

PropertyTypeDescription
imageBytesbytesFace photo (JPEG)
confidencedoubleDetection confidence (0-1)
widthintImage width px
heightintImage height px
bboxdouble[]Bounding box [x1, y1, x2, y2]

result FaceResult

Selfie vs document face match (inside DocumentResult.faceVerification)

PropertyTypeDescription
isMatchboolFaces match (selfie = document photo)
similarityScoredoubleSimilarity percentage (0-100%)
thresholddoubleMatch threshold used
confidenceLevelStringVERY_HIGH | HIGH | MEDIUM | LOW
detectionModelStringModel used (e.g., scrfd_10g)
recognitionModelStringModel used (e.g., buffalo_l)
processingTimeMsintFace comparison time in ms

Documents Supportes

Champs extractibles par type de document

Carte d'Identite CEDEAO

SN-CIN

Cle Label Format Requis
numero_carteN° Carte d'identiteX XXXX AAAA XXXXXOui
prenomsPrenomsTexteOui
nomNomTexteOui
date_naissanceDate de naissanceJJ/MM/AAAAOui
sexeSexeM / FOui
tailleTailleX,XX mNon
lieu_naissanceLieu de naissanceTexteNon
date_delivranceDate de delivranceJJ/MM/AAAANon
date_expirationDate d'expirationJJ/MM/AAAAOui
centre_enregistrementCentre d'enregistrementTexteNon
adresse_domicileAdresse du domicileTexteNon
birth_regionRegion de naissance (derive)DeduitAuto
Cle Label Format Requis
code_paysCode PaysSEN (fixe)Oui
Informations electorales — presents si l'electeur est inscrit, sinon null
inscrit_liste_electoraleInscrit sur la liste electoraletrue / falseNon
numero_electeurNumero d'electeurchiffresNon
regionRegionTexteNon
departementDepartementTexteNon
arrondissementArrondissementTexteNon
communeCommuneTexteNon
lieu_de_voteLieu de voteTexteNon
bureau_de_voteBureau de voteNumeroNon
ninNIN (Numero d'Identification Nationale)13 chiffres sans espacesOui
mrzMRZ (Machine Readable Zone)3 lignes x 30 charsOui
Structure MRZ (ICAO 9303)
L1: I<SENNUMERO_CARTE<<<<<<
L2: AAMMJJCMAAMMJJCSEN<<<<<<<<<<<C
L3: NOM<<PRENOMS<<<<<<<<<<<<

Passeport Biometrique

SN-PASSPORT

Recto uniquement
Cle Label Format Requis
numero_passeportN° PasseportAXXXXXXXXOui
nomNomUPPERCASEOui
prenomsPrenomsUPPERCASEOui
nationaliteNationaliteSENEGALAISEOui
date_naissanceDate de naissanceDD MMM YYYYOui
ninN° personnel (NIN)13 chiffresOui
sexeSexeM / FOui
lieu_naissanceLieu de naissanceTexteOui
autoriteAutoriteTexteOui
date_delivranceDate de delivranceDD MMM YYYYOui
date_expirationDate d'expirationDD MMM YYYYOui
mrzMRZ (Machine Readable Zone)2 lignes x 44 charsOui
Structure MRZ Passeport (ICAO 9303 TD3)
L1: P<SENNOM<<PRENOMS<<<<<<<<<<<<<<<<<<<<<<
L2: NUMERO_PASSSENAAMMJJCMAAMMJJCNIN<<<<CC
NUMERO_PASS = Numero passeport (9 chars)
AAMMJJ = Date (naissance / expiration)
M = Sexe (M/F)
NIN = N° personnel (13 digits)
C = Check digit
Caracteristiques
  • Format: ID-3 (125mm x 88mm)
  • Pages: 48
  • Materiau: Polycarbonate
  • Photo: Couleur, fond bleu
Cross-validation CIN
  • NIN doit correspondre au verso CIN
  • Nom/Prenoms identiques
  • Date de naissance identique
  • Sexe identique

Permis de Conduire

SN-DRIVER-LICENCE

Recto + Verso
Cle Label Format Requis
numero_permisN° PermisXXXXXXXXOui
nomNomTexteOui
prenomsPrenomsTexteOui
date_naissanceDate et lieu de naissanceDD/MM/YYYY + VilleOui
date_emissionDate d'emissionDD/MM/YYYYOui
date_expirationDate d'expirationDD/MM/YYYYOui
delivre_parDelivre parMITTDOui
categoriesCategoriesA1 B etcOui
groupe_sanguinGroupe sanguinO+ A- B+Non
Cle Label Format Requis
adresseAdresseTexteOui
communeCommuneTexteOui
departementDepartementTexteOui
regionRegionTexteOui
sexeSexeM / FOui
categories_activesCategories actives (derive)["A1", "B"]Auto
mrzMRZD1SN...Oui
Tableau des Categories (colonnes 9-12)
9. Categorie
10. Date obtention
11. Date expiration
12. Restrictions
A1 🏍️10/11/2109/11/31-
B 🚗10/11/2109/11/31-
C 🚛---
Structure MRZ Permis (1 ligne)
MRZ: D1SN10804940NDOUR<<<<<<<<<<<6
D1 = Document type (driving license)
SN = Country code (Senegal)
License number
Last name
Check digit

Modeles IA - InsightFace

Tous les modeles utilisent ONNX Runtime pour des performances optimales.

Modeles de Detection (SCRFD)

Modele FLOPs Vitesse Precision Usage
scrfd_10g 10G 4.9ms 95.16% Production
scrfd_2.5g 2.5G 4.2ms 93.78% Balance
scrfd_500m 500M ~2ms ~90% Mobile

Modeles de Reconnaissance (ArcFace / GLint)

Glossaire

Backbones (Architectures)

  • ResNet50 - Reseau de neurones a 50 couches. Bon equilibre precision/vitesse.
  • ResNet100 - Reseau a 100 couches. Plus precis mais plus lent.
  • MobileFaceNet - Architecture legere optimisee pour mobile. Tres rapide.

Datasets (Donnees d'entrainement)

  • WebFace600K - 600 000 identites, ~10M images de visages.
  • GLint360K - 360 000 identites, 17M images. Dataset plus large.
  • MS1MV2 - MS-Celeb-1M nettoye. ~5.8M images, 85K identites.

Convention de nommage des modeles

  • w600k_r50 = WebFace600K + ResNet50 (ArcFace standard)
  • w600k_mbf = WebFace600K + MobileFaceNet (version mobile)
  • glintr100 = GLint360K + ResNet100 (haute precision)
Pack Modele Backbone Dataset LFW Usage
buffalo_l w600k_r50 ResNet50 MS1MV2 (600K ids) 99.8% Production
buffalo_s w600k_mbf MobileFaceNet MS1MV2 (600K ids) 99.5% Mobile / Fast
antelopev2 glintr100 ResNet100 GLint360K (17M imgs) 99.8% Alternative

Recommandations par region

Afrique / Senegal
Detection: scrfd_10g
Recognition: w600k_r50
Mobile / Edge
Detection: scrfd_500m
Recognition: w600k_mbf
Haute precision
Pack: buffalo_l
Recognition: w600k_r50

Face Verification Playground

Testez l'API de verification faciale.

POST /api/v1/verify/face

Visage de reference

Selfie ou photo

Uploadez deux images et cliquez sur Verifier

Face Detection Playground

Detectez les visages dans une image et obtenez les bounding boxes.

POST /api/v1/detect/face

Glissez ou cliquez pour uploader

Uploadez une image et cliquez sur Detecter

Cles API Temporaires

Generez des cles API temporaires pour vos SDK mobiles. Les cles temporaires heritent des permissions de la cle parente et expirent automatiquement. Seules les cles principales peuvent generer/revoquer des cles temporaires.

POST /api/v1/gen_key
POST /api/v1/revoke_key

Securite

  • • Les cles temporaires ne peuvent PAS generer d'autres cles
  • • Les cles temporaires ne peuvent PAS revoquer d'autres cles
  • • Seules les cles principales (master) ont ces privileges
  • • Les cles expirent automatiquement (max 24h)

Documents API

Listez les documents d'identite supportes avec leurs champs, elements visuels et de securite.

GET /api/v1/documents

Cliquez sur Charger (API Key requise ci-dessus)

Usage API

Consultez les statistiques d'utilisation de votre application avec suivi mensuel.

GET /api/v1/usage

Retourne les statistiques d'utilisation a deux niveaux:

  • application_usage: Usage global de l'application (toutes les cles confondues)
  • api_keys_usage: Usage par cle API master (pas les cles temporaires)

Cliquez sur Charger (API Key requise ci-dessus)

Compteurs disponibles

liveness_sessions: Sessions WebSocket
selfie: Analyses selfie
recto: Analyses recto
verso: Analyses verso
face_verifications: Verifications visage
total_sessions: Total sessions KYC

Webhooks

Recevez des notifications en temps reel apres chaque etape et a la fin de chaque session KYC.

Configuration

Configurez votre webhook dans les settings de votre application:

{
  "settings": {
    "webhook_url": "https://your-api.com/kyvshield/webhook"
  }
}

Note: La signature HMAC utilise l'API key qui a initie la session comme secret.

Headers HTTP

POST     https://your-api.com/kyvshield/webhook
Host     your-api.com

x-kyvshield-timestamp: 2026-03-15T04:11:36Z
x-kyvshield-session: 468d970da539365c7f704ebd8f246cb4
x-kyvshield-event: selfie.completed
x-kyvshield-signature: sha256=<HMAC-SHA256 du body avec API key>
content-type: application/json
user-agent: KyvShield-Webhook/1.0

Evenements

selfie.completed

Selfie verifie avec succes

recto.completed

Recto analyse avec succes

verso.completed

Verso analyse avec succes

session.completed

Session KYC terminee avec succes

selfie.failed

Selfie rejete (non vivant/fraude)

recto.failed

Recto rejete (fraude detectee)

verso.failed

Verso rejete (fraude detectee)

session.failed

Session echouee ou timeout

selfie.completed Liveness

Envoye apres la verification liveness du selfie.

{
  "event": "selfie.completed",
  "timestamp": "2026-03-15T04:11:36Z",
  "session_id": "468d970da539365c7f704ebd8f246cb4",
  "app_id": "demo_app",
  "key_id": "demo_key_1",
  "document_type": "SN-CIN",
  "step_data": {
    "captured_image": "/9j/4AAQSkZJRgABAQAAAQABAAD/...<base64>...",
    "liveness": {
      "confidence": "HIGH",
      "is_live": true,
      "score": 0.95
    },
    "processing_time_ms": 8907,
    "step_index": 0,
    "step_type": "selfie",
    "success": true,
    "verification": {
      "checks_passed": ["center_face", "close_eyes"],
      "confidence": 0.95,
      "fraud_indicators": [],
      "is_authentic": true
    }
  }
}

recto.completed OCR + Fraud

Envoye apres l'analyse du recto du document d'identite. Inclut OCR, detection de visage, et verification anti-fraude.

{
  "event": "recto.completed",
  "timestamp": "2026-03-15T04:57:25Z",
  "session_id": "5b5f09cd882170f81889dc9448bf43c6",
  "app_id": "demo_app",
  "key_id": "demo_key_1",
  "document_type": "SN-CIN",
  "step_data": {
    "aligned_document": "/9j/4AAQSkZJRgABAQAAAQABAAD/...<base64 JPEG>...",
    "detected_faces": [
      {
        "image": "/9j/4AAQSkZJRgABAQAAAQABAAD/...<base64>...",
        "confidence": 0.828,
        "bbox": [96, 222, 210, 373],
        "area": 17214,
        "width": 114,
        "height": 151
      }
    ],
    "extracted_photos": [
      {
        "image": "/9j/4AAQSkZJRgABAQAAAQABAAD/...<base64 JPEG>...",
        "confidence": 0.95,
        "bbox": [96, 222, 210, 373],
        "area": 17214,
        "width": 114,
        "height": 151
      }
    ],
    "extraction": [
      {"key": "document_id", "document_key": "numero_carte", "label": "N° de la carte", "value": "1 06 19930515 00026 8"},
      {"key": "first_name", "document_key": "prenoms", "label": "Prenoms", "value": "MOUSSA"},
      {"key": "last_name", "document_key": "nom", "label": "Nom", "value": "NDOUR"},
      {"key": "birth_date", "document_key": "date_naissance", "label": "Date de naissance", "value": "15/05/1993"},
      {"key": "sex", "document_key": "sexe", "label": "Sexe", "value": "M"},
      {"key": "height", "document_key": "taille", "label": "Taille", "value": "175 cm"},
      {"key": "birth_place", "document_key": "lieu_naissance", "label": "Lieu de naissance", "value": "KAOLACK"},
      {"key": "issue_date", "document_key": "date_delivrance", "label": "Date de delivrance", "value": "29/07/2019"},
      {"key": "expiry_date", "document_key": "date_expiration", "label": "Date d'expiration", "value": "28/07/2029"},
      {"key": "issuing_authority", "document_key": "centre_enregistrement", "label": "Centre", "value": "COMM. DE DIEUPPEUL"}
    ],
    "liveness": {
      "confidence": "HIGH",
      "is_live": true,
      "score": 0.92
    },
    "processing_time_ms": 11631,
    "step_type": "recto",
    "success": true,
    "verification": {
      "checks_passed": ["center_document", "tilt_left", "tilt_right"],
      "confidence": 0.92,
      "fraud_indicators": [],
      "is_authentic": true
    }
  }
}

Note: aligned_document = image redresse, detected_faces = visages detectes par ML, extracted_photos = photos extraites du document (pour face match), extraction = donnees OCR.

verso.completed NIN + MRZ

Envoye apres l'analyse du verso. Contient le NIN et le MRZ.

{
  "event": "verso.completed",
  "timestamp": "2026-03-15T05:00:21Z",
  "session_id": "d59dc22f9e792aba85637150aef25cee",
  "app_id": "demo_app",
  "key_id": "demo_key_1",
  "document_type": "SN-CIN",
  "step_data": {
    "aligned_document": "/9j/4AAQSkZJRgABAQAAAQABAAD/...<base64>...",
    "extraction": [
      {"key": "code_pays", "label": "Code Pays", "value": "SEN"},
      {"key": "inscrit_liste_electorale", "label": "Inscrit liste electorale", "value": true},
      {"key": "numero_electeur", "label": "Numero d'electeur", "value": "100656150"},
      {"key": "region", "label": "Region", "value": "DAKAR"},
      {"key": "departement", "label": "Departement", "value": "GUEDIAWAYE"},
      {"key": "arrondissement", "label": "Arrondissement", "value": "GUEDIAWAYE"},
      {"key": "commune", "label": "Commune", "value": "GOLF SUD"},
      {"key": "lieu_de_vote", "label": "Lieu de vote", "value": "LYCEE PARCELLES ASSAINIES GUEDIAWAY"},
      {"key": "bureau_de_vote", "label": "Bureau de vote", "value": "01"},
      {"key": "national_id", "document_key": "nin", "label": "NIN", "value": "1765198311101"},
      {"key": "mrz", "label": "MRZ", "value": "I<SEN101198304<...<3 lignes MRZ>..."}
    ],
    "liveness": {
      "confidence": "HIGH",
      "is_live": true,
      "score": 0.92
    },
    "processing_time_ms": 12308,
    "step_type": "verso",
    "success": true,
    "verification": {
      "checks_passed": ["center_document"],
      "confidence": 0.92,
      "fraud_indicators": [],
      "is_authentic": true
    }
  }
}

session.completed Final

Envoye quand toute la session KYC est terminee avec succes.

{
  "event": "session.completed",
  "timestamp": "2026-03-15T04:11:36Z",
  "session_id": "468d970da539365c7f704ebd8f246cb4",
  "app_id": "demo_app",
  "key_id": "demo_key_1",
  "document_type": "SN-CIN",
  "final_result": {
    "success": true,
    "status": "PASS",
    "steps": {
      "face_match_passed": true,
      "face_match_score": 0.87,
      "overall_confidence": 0.95,
      "processing_time_ms": 8907,
      "rejection_reason": "",
      "steps_completed": 3,
      "steps_failed": 0,
      "steps_passed": 3
    }
  }
}

session.failed Erreur

Envoye quand la session expire ou echoue.

{
  "event": "session.failed",
  "timestamp": "2026-03-15T04:09:45Z",
  "session_id": "...",
  "app_id": "demo_app",
  "key_id": "demo_key_1",
  "document_type": "SN-CIN",
  "final_result": {
    "success": false,
    "status": "TIMEOUT",
    "steps": {
      "reason": "session_expired"
    }
  }
}

Verification de signature HMAC

Le header X-KyvShield-Signature contient la signature HMAC-SHA256 du body JSON. Le secret est l'API key qui a cree la session.

Python
import hmac
import hashlib

def verify_signature(payload_bytes: bytes, api_key: str, signature_header: str) -> bool:
    """
    Verifie la signature HMAC du webhook.

    Args:
        payload_bytes: Le body JSON brut du webhook
        api_key: L'API key qui a cree la session
        signature_header: La valeur du header X-KyvShield-Signature

    Returns:
        True si la signature est valide
    """
    # Le header est au format "sha256=<hex>"
    if not signature_header.startswith("sha256="):
        return False

    expected_sig = signature_header[7:]  # Remove "sha256=" prefix

    # Calcul du HMAC-SHA256
    computed = hmac.new(
        api_key.encode('utf-8'),
        payload_bytes,
        hashlib.sha256
    ).hexdigest()

    return hmac.compare_digest(computed, expected_sig)

# Usage Flask/FastAPI:
# @app.post("/webhook")
# def handle_webhook(request: Request):
#     signature = request.headers.get("X-KyvShield-Signature", "")
#     if not verify_signature(request.body(), "your_api_key", signature):
#         raise HTTPException(401, "Invalid signature")
#     data = request.json()
#     # Process webhook...
Go
package main

import (
    "crypto/hmac"
    "crypto/sha256"
    "encoding/hex"
    "strings"
)

// VerifySignature verifie la signature HMAC du webhook
func VerifySignature(payloadBytes []byte, apiKey, signatureHeader string) bool {
    // Le header est au format "sha256=<hex>"
    if !strings.HasPrefix(signatureHeader, "sha256=") {
        return false
    }
    expectedSig := signatureHeader[7:] // Remove "sha256=" prefix

    // Calcul du HMAC-SHA256
    mac := hmac.New(sha256.New, []byte(apiKey))
    mac.Write(payloadBytes)
    computed := hex.EncodeToString(mac.Sum(nil))

    return hmac.Equal([]byte(computed), []byte(expectedSig))
}
Node.js
const crypto = require('crypto');

function verifySignature(payloadBuffer, apiKey, signatureHeader) {
    // Le header est au format "sha256=<hex>"
    if (!signatureHeader.startsWith('sha256=')) {
        return false;
    }
    const expectedSig = signatureHeader.slice(7);

    // Calcul du HMAC-SHA256
    const computed = crypto
        .createHmac('sha256', apiKey)
        .update(payloadBuffer)
        .digest('hex');

    return crypto.timingSafeEqual(
        Buffer.from(computed),
        Buffer.from(expectedSig)
    );
}

// Usage Express:
// app.post('/webhook', express.raw({type: '*/*'}), (req, res) => {
//     const sig = req.headers['x-kyvshield-signature'];
//     if (!verifySignature(req.body, 'your_api_key', sig)) {
//         return res.status(401).send('Invalid signature');
//     }
//     const data = JSON.parse(req.body);
//     // Process webhook...
// });

Politique de retry

  • • Max 3 tentatives avec backoff exponentiel (1s, 5s, 30s)
  • • Timeout de 10 secondes par requete
  • • Les echecs sont logues et conserves 24h
  • • Votre endpoint doit retourner un code 2xx pour confirmer la reception