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

SDKs Serveur (API REST)

Verification KYC serveur-a-serveur, sans WebSocket

Quand utiliser les SDKs serveur ?

Les SDKs serveur appellent l'API REST KyvShield directement depuis votre backend. Ils sont ideaux pour le traitement par lots, les pipelines CI/CD, ou tout workflow ou les images sont deja disponibles cote serveur. Pas de camera, pas de WebSocket — juste des fichiers et une reponse JSON.

Mono-repo : github.com/moussa-innolink/kyv_shield_restfull

verify()
Verification unitaire
verifyBatch()
Traitement par lots
getChallenges()
Liste des challenges
verifyWebhookSignature()
Valider webhook

Installation

npm install @kyvshield/rest-sdk

Exemple complet

import { KyvShield } from '@kyvshield/rest-sdk';
import { readFileSync } from 'fs';

const client = new KyvShield('YOUR_API_KEY', 'https://kyvshield-naruto.innolinkcloud.com');

// ── Get available challenges per mode ─────────────────────────────────
const challenges = await client.getChallenges();
console.log(challenges.challenges.document);  // { minimal: ['center_document'], standard: [...], strict: [...] }
console.log(challenges.challenges.selfie);    // { minimal: ['center_face','close_eyes'], ... }

// ── Single verification (recto + verso, minimal mode) ────────────────
const result = await client.verify({
  steps:             ['recto', 'verso'],
  target:            'SN-CIN',
  language:          'fr',
  challengeMode:     'minimal',
  rectoChallengeMode:'minimal',
  versoChallengeMode:'minimal',
  images: {
    recto_center_document: './images/cin_recto.jpg',      // file path
    verso_center_document: './images/cin_verso.jpg',      // file path
  },
});

console.log(result.session_id);          // "468d970da539..."
console.log(result.overall_status);      // "pass" | "reject"
console.log(result.overall_confidence);  // 0.95

for (const step of result.steps) {
  console.log(`${step.step_type}: ${step.liveness?.score}`);
  for (const f of step.extraction ?? [])
    console.log(`  ${f.label}: ${f.value}`);
}

// ── Standard mode (3 images per step) ────────────────────────────────
const result2 = await client.verify({
  steps: ['recto'], target: 'SN-CIN', challengeMode: 'standard',
  images: {
    recto_center_document: './recto_flat.jpg',
    recto_tilt_left:       './recto_tilted_l.jpg',
    recto_tilt_right:      './recto_tilted_r.jpg',
  },
});

// ── Images can be: file path, URL, Buffer, base64, data URI ──────────
const result3 = await client.verify({
  steps: ['recto'], target: 'SN-CIN', challengeMode: 'minimal',
  images: {
    recto_center_document: readFileSync('./recto.jpg'),  // Buffer
    // or: 'https://example.com/recto.jpg'              // URL
    // or: 'data:image/jpeg;base64,/9j/4AAQ...'         // data URI
    // or: readFileSync('./recto.jpg').toString('base64') // base64
  },
});

// ── Batch verification (up to 10 concurrent) ─────────────────────────
const batch = await client.verifyBatch([
  { steps: ['recto'], target: 'SN-CIN', challengeMode: 'minimal',
    images: { recto_center_document: './batch/01.jpg' } },
  { steps: ['recto'], target: 'SN-CIN', challengeMode: 'minimal',
    images: { recto_center_document: './batch/02.jpg' } },
]);

// ── Verify webhook signature ─────────────────────────────────────────
const isValid = KyvShield.verifyWebhookSignature(
  req.body,               // Buffer (raw body)
  'YOUR_API_KEY',
  req.headers['x-kyvshield-signature'],
);

Source: GitHub (nodejs/)

Installation

composer require kyvshield/rest-sdk

Exemple complet

<?php

require_once 'vendor/autoload.php';

use KyvShield\KyvShield;
use KyvShield\VerifyOptions;

$client = new KyvShield('YOUR_API_KEY', 'https://kyvshield-naruto.innolinkcloud.com');

// ── Get available challenges per mode ─────────────────────────────────
$challenges = $client->getChallenges();
$docMinimal = $challenges->getChallenges('document', 'minimal');  // ['center_document']

// ── Verify recto + verso (file paths) ────────────────────────────────
$result = $client->verify(new VerifyOptions(
    steps:             ['recto', 'verso'],
    target:            'SN-CIN',
    language:          'fr',
    challengeMode:     'minimal',
    stepChallengeModes:['recto' => 'minimal', 'verso' => 'minimal'],
    images: [
        'recto_center_document' => '/path/to/cin_recto.jpg',  // file path
        'verso_center_document' => '/path/to/cin_verso.jpg',  // file path
    ],
));

echo $result->sessionId;          // "468d970da539..."
echo $result->overallStatus;       // "pass" | "reject"
echo $result->overallConfidence;   // 0.95

foreach ($result->steps as $step) {
    echo "{$step->stepType}: {$step->liveness->score}\n";
    foreach ($step->extraction ?? [] as $f)
        echo "  {$f->label}: {$f->value}\n";
}

// ── Images can also be: URL, base64, data URI ────────────────────────
$result2 = $client->verify(new VerifyOptions(
    steps: ['recto'], target: 'SN-CIN', challengeMode: 'minimal',
    images: [
        'recto_center_document' => 'https://example.com/recto.jpg',         // URL
        // or: base64_encode(file_get_contents('./recto.jpg'))            // base64
        // or: 'data:image/jpeg;base64,' . base64_encode($bytes)          // data URI
    ],
));

// ── Batch verification (up to 10 concurrent) ─────────────────────────
$batch = $client->verifyBatch([
    new VerifyOptions(steps: ['recto'], target: 'SN-CIN', challengeMode: 'minimal',
        images: ['recto_center_document' => '/batch/01.jpg']),
    new VerifyOptions(steps: ['recto'], target: 'SN-CIN', challengeMode: 'minimal',
        images: ['recto_center_document' => '/batch/02.jpg']),
]);

// ── Verify webhook signature ─────────────────────────────────────────
$isValid = KyvShield::verifyWebhookSignature(
    file_get_contents('php://input'),
    'YOUR_API_KEY',
    $_SERVER['HTTP_X_KYVSHIELD_SIGNATURE'],
);

Source: GitHub (php/)

Installation (Gradle + JitPack)

// settings.gradle
dependencyResolutionManagement {
repositories {
maven { url 'https://jitpack.io' }
}
}
// build.gradle
dependencies {
implementation 'com.github.moussa-innolink.kyv_shield_restfull:java:1.2.1'
}

Exemple complet

import sn.innolink.kyvshield.KyvShield;
import sn.innolink.kyvshield.model.*;
import java.nio.file.Files;
import java.nio.file.Paths;

public class Main {
    public static void main(String[] args) throws Exception {
        KyvShield client = new KyvShield("YOUR_API_KEY", "https://kyvshield-naruto.innolinkcloud.com");

        // ── Verify recto + verso (file paths) ─────────────────────────
        KycResponse result = client.verify(
            new VerifyOptions.Builder()
                .steps(Step.RECTO, Step.VERSO)
                .target("SN-CIN")
                .language("fr")
                .challengeMode(ChallengeMode.MINIMAL)
                .rectoChallengeMode(ChallengeMode.MINIMAL)
                .versoChallengeMode(ChallengeMode.MINIMAL)
                .addImage("recto_center_document", "./images/cin_recto.jpg")   // file path
                .addImage("verso_center_document", "./images/cin_verso.jpg")   // file path
                .build()
        );

        System.out.println(result.getSessionId());        // "468d970da539..."
        System.out.println(result.getOverallStatus());     // PASS | REJECT
        System.out.printf("confidence: %.4f%n", result.getOverallConfidence());

        for (StepResult step : result.getSteps()) {
            System.out.printf("%s: score=%.4f%n", step.getStepType(), step.getLiveness().getScore());
            if (step.getExtraction() != null)
                step.getExtraction().forEach(f ->
                    System.out.printf("  %s: %s%n", f.getLabel(), f.getValue()));
        }

        // ── With raw bytes ────────────────────────────────────────────
        byte[] rectoBytes = Files.readAllBytes(Paths.get("./recto.jpg"));
        KycResponse result2 = client.verify(
            new VerifyOptions.Builder()
                .steps(Step.RECTO).target("SN-CIN")
                .challengeMode(ChallengeMode.MINIMAL)
                .addImageBytes("recto_center_document", rectoBytes)  // byte[]
                .build()
        );

        // ── Batch (up to 10 concurrent) ───────────────────────────────
        var batch = client.verifyBatch(java.util.List.of(
            new VerifyOptions.Builder().steps(Step.RECTO).target("SN-CIN")
                .challengeMode(ChallengeMode.MINIMAL)
                .addImage("recto_center_document", "./batch/01.jpg").build(),
            new VerifyOptions.Builder().steps(Step.RECTO).target("SN-CIN")
                .challengeMode(ChallengeMode.MINIMAL)
                .addImage("recto_center_document", "./batch/02.jpg").build()
        ));

        // ── Webhook signature verification ────────────────────────────
        boolean valid = KyvShield.verifyWebhookSignature(rawBody, "YOUR_API_KEY", sigHeader);
    }
}

Source: GitHub (java/)JitPack

Installation (Gradle + JitPack)

// settings.gradle.kts
dependencyResolutionManagement {
repositories {
maven { url = uri("https://jitpack.io") }
}
}
// build.gradle.kts
dependencies {
implementation("com.github.moussa-innolink.kyv_shield_restfull:kotlin:1.2.1")
}

Exemple complet

import sn.innolink.kyvshield.KyvShield
import sn.innolink.kyvshield.model.*
import java.io.File

fun main() {
    val client = KyvShield("YOUR_API_KEY", "https://kyvshield-naruto.innolinkcloud.com")

    // ── Verify recto + verso (file paths) ─────────────────────────
    val result = client.verify(
        VerifyOptions(
            steps             = listOf(Step.RECTO, Step.VERSO),
            target            = "SN-CIN",
            language          = Language.FRENCH,
            challengeMode     = ChallengeMode.MINIMAL,
            stepChallengeModes = mapOf("recto" to ChallengeMode.MINIMAL, "verso" to ChallengeMode.MINIMAL),
            images = mapOf(
                "recto_center_document" to "./images/cin_recto.jpg",   // file path
                "verso_center_document" to "./images/cin_verso.jpg",   // file path
            ),
        )
    )

    println("session: ${result.sessionId}")         // "468d970da539..."
    println("status: ${result.overallStatus}")       // PASS | REJECT
    println("confidence: ${result.overallConfidence}")

    result.steps.forEach { step ->
        println("${step.stepType}: ${step.liveness.score}")
        step.extraction?.sortedBy { it.displayPriority }?.forEach { f ->
            println("  ${f.label}: ${f.value}")
        }
    }

    // ── With raw bytes (ByteArray) ────────────────────────────────
    val rectoBytes = File("./recto.jpg").readBytes()
    val result2 = client.verify(VerifyOptions(
        steps = listOf(Step.RECTO), target = "SN-CIN", challengeMode = ChallengeMode.MINIMAL,
        imageBytes = mapOf("recto_center_document" to rectoBytes),  // ByteArray
    ))

    // ── Batch (up to 10 concurrent) ───────────────────────────────
    val batch = client.verifyBatch(listOf(
        VerifyOptions(steps = listOf(Step.RECTO), target = "SN-CIN", challengeMode = ChallengeMode.MINIMAL,
            images = mapOf("recto_center_document" to "./batch/01.jpg")),
        VerifyOptions(steps = listOf(Step.RECTO), target = "SN-CIN", challengeMode = ChallengeMode.MINIMAL,
            images = mapOf("recto_center_document" to "./batch/02.jpg")),
    ))

    // ── Webhook signature verification ────────────────────────────
    val valid = KyvShield.verifyWebhookSignature(rawBody, "YOUR_API_KEY", sigHeader)
}

Source: GitHub (kotlin/)JitPack

Installation

go get github.com/moussa-innolink/kyv_shield_restfull/go

Exemple complet

package main

import (
    "context"
    "fmt"
    "log"
    "os"

    kyvshield "github.com/moussa-innolink/kyv_shield_restfull/go"
)

func main() {
    client := kyvshield.NewClient("YOUR_API_KEY", kyvshield.WithBaseURL("https://kyvshield-naruto.innolinkcloud.com"))
    ctx := context.Background()

    // ── Verify recto + verso (file paths) ─────────────────────────
    resp, err := client.Verify(ctx, &kyvshield.VerifyOptions{
        Steps:         []string{"recto", "verso"},
        Target:        "SN-CIN",
        Language:      "fr",
        ChallengeMode: "minimal",
        Images: map[string]string{
            "recto_center_document": "./images/cin_recto.jpg",   // file path
            "verso_center_document": "./images/cin_verso.jpg",   // file path
        },
    })
    if err != nil { log.Fatal(err) }

    fmt.Println(resp.SessionID)         // "468d970da539..."
    fmt.Println(resp.OverallStatus)     // "pass" | "reject"
    fmt.Printf("confidence: %.4f\n", resp.OverallConfidence)

    for _, step := range resp.Steps {
        fmt.Printf("%s: score=%.4f\n", step.StepType, step.Liveness.Score)
        for _, f := range step.Extraction {
            fmt.Printf("  %s: %s\n", f.Label, f.Value)
        }
    }

    // ── With raw bytes (ImageBytes map) ───────────────────────────
    rectoBytes, _ := os.ReadFile("./recto.jpg")
    versoBytes, _ := os.ReadFile("./verso.jpg")
    resp2, _ := client.Verify(ctx, &kyvshield.VerifyOptions{
        Steps: []string{"recto", "verso"}, Target: "SN-CIN", ChallengeMode: "minimal",
        ImageBytes: map[string][]byte{
            "recto_center_document": rectoBytes,   // []byte
            "verso_center_document": versoBytes,   // []byte
        },
    })
    _ = resp2

    // ── Images also accept: URL, base64, data URI ────────────────
    // Images: map[string]string{
    //   "recto_center_document": "https://example.com/recto.jpg",        // URL
    //   "recto_center_document": "data:image/jpeg;base64,/9j/4AA...",   // data URI
    //   "recto_center_document": "/9j/4AAQSkZJRg...",                   // base64
    // }

    // ── Batch (up to 10 concurrent) ───────────────────────────────
    batch, _ := client.VerifyBatch(ctx, []*kyvshield.VerifyOptions{
        {Steps: []string{"recto"}, Target: "SN-CIN", ChallengeMode: "minimal",
         Images: map[string]string{"recto_center_document": "./batch/01.jpg"}},
        {Steps: []string{"recto"}, Target: "SN-CIN", ChallengeMode: "minimal",
         Images: map[string]string{"recto_center_document": "./batch/02.jpg"}},
    })
    _ = batch

    // ── Webhook signature verification ────────────────────────────
    valid := kyvshield.VerifyWebhookSignature(rawBody, "YOUR_API_KEY", sigHeader)
    _ = valid
}

Source: GitHub (go/)

API REST sous-jacente

Element Valeur
Endpoint POST /api/v1/kyc/verify
Content-Type multipart/form-data
Authentification X-API-Key: YOUR_API_KEY
Champs texte steps (JSON array), target, language, challenge_mode, require_face_match ("true"/"false", default "false"), require_aml ("true"/"false", default "false" — app must have allow_aml enabled)
Champs fichier Format: {step}_{challenge} e.g. recto_center_document, selfie_center_face, recto_tilt_left

Images requises par mode

Step Mode Champs image
selfieminimalselfie_center_face, selfie_close_eyes
selfiestandardselfie_center_face, selfie_close_eyes, selfie_turn_left, selfie_turn_right
selfiestrict+ selfie_smile, selfie_look_up, selfie_look_down
recto/versominimalrecto_center_document
recto/versostandardrecto_center_document, recto_tilt_left, recto_tilt_right
recto/versostrict+ recto_tilt_forward, recto_tilt_back

Formats d'image acceptes par les SDKs

Format Node.js PHP Java Kotlin Go
Chemin fichier images[key] images[key] addImage(key, path) images[key] Images[key]
URL (http/https) images[key] images[key] addImage(key, url) images[key] Images[key]
Base64 images[key] images[key] addImage(key, b64) images[key] Images[key]
Data URI images[key] images[key] addImage(key, uri) images[key] Images[key]
Buffer / bytes Buffer base64_encode() addImageBytes(key, byte[]) imageBytes[key] (ByteArray) ImageBytes[key] ([]byte)
Note : Tous les SDKs detectent automatiquement le format d'entree. L'auto-detection suit cette priorite : Buffer/bytes > URL (http) > Data URI > Base64 (chaine longue sans /) > Chemin fichier.

Reponse

{
  "success":            true,
  "session_id":         "468d970da539...",
  "overall_status":     "pass",
  "overall_confidence": 0.95,
  "processing_time_ms": 2450,
  "steps": [
    {
      "step_type": "recto", "success": true,
      "liveness": { "is_live": true, "score": 0.95, "confidence": "HIGH" },
      "verification": { "is_authentic": true, "confidence": 0.96 },
      "extraction": [
        { "key": "document_id", "label": "N° carte", "value": "1 06 19930515 00026 8", "display_priority": 1 },
        { "key": "first_name", "label": "Prenoms", "value": "MOUSSA", "display_priority": 2 }
      ]
    }
  ]
}

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)KycResultLance le flux KYC complet. Retourne le resultat quand l'utilisateur termine ou annule.
checkCameraPermission()boolVerifie et demande la permission camera. Retourne true si accordee.
isCameraPermissionPermanentlyDenied()boolRetourne true si la camera a ete refusee definitivement. L'utilisateur doit aller dans Reglages.
openSettings()boolOuvre les reglages du systeme pour que l'utilisateur active manuellement la camera.

config KyvshieldConfig

Property Type Default Description
baseUrlStringrequisURL du serveur API
apiKeyStringrequisCle API du tableau de bord
apiVersionString'v1'Version de l'API
timeoutSecondsint60Timeout HTTP (pas le timeout de session)
enableLogboolfalseActiver les logs de debug
themeThemeConfig?nullTheme personnalise (voir ci-dessous)
acceptReviewStatusboolfalseAccepter le statut REVIEW comme valide

flow KyvshieldFlowConfig

Property Type Default Description
stepsCaptureStep[][selfie, recto, verso]Etapes a executer (l'ordre est important)
targetString?null (auto-detect)Type de document : SN-CIN, SN-PASSPORT, SN-DRIVER-LICENCE
challengeModeChallengeModestandardminimal (1) | standard (3) | strict (5+)
languageString'fr'fr | en | wo
showIntroPagebooltrueAfficher l'ecran d'introduction avant le KYC
showInstructionPagesbooltrueAfficher les instructions avant chaque etape
showResultPagebooltrueAfficher l'ecran de resultat apres le KYC
showSuccessPerStepbooltrueAfficher l'animation de succes apres chaque etape
requireFaceMatchbooltrueComparer le selfie avec la photo du document
selfieDisplayModeDisplayModestandardstandard | compact | immersive
documentDisplayModeDisplayModestandardstandard | compact | immersive
playChallengeAudioboolfalseJouer les instructions vocales pour chaque defi
maxChallengeAudioPlayint1Repeter l'audio 1, 2 ou 3 fois
kycIdentifierString?nullVotre ID de reference (retourne dans les webhooks)
stringsStrings?nullRemplacer n'importe quel texte de l'interface
Presets de flux (Flutter)
KyvshieldFlowConfig.selfieOnly()

Selfie uniquement, sans document

KyvshieldFlowConfig.standard()

Selfie + recto (recommande)

KyvshieldFlowConfig.full()

Selfie + recto + verso

KyvshieldFlowConfig.quick()

Sans pages UI, defis minimaux

KyvshieldFlowConfig.strict()

Securite maximale (5+ defis)

KyvshieldFlowConfig.documentOnly()

Recto + verso, sans selfie

ThemeConfig

Property Type Description
primaryColorColor / StringCouleur principale (boutons, accents, indicateurs)
successColorColor?Couleur de succes (defaut : #10B981)
warningColorColor?Couleur d'avertissement (defaut : #F59E0B)
errorColorColor?Couleur d'erreur (defaut : #EF4444)
themeModeStringlight | dark | auto
Presets
Innolink#EF8352
Blue#3B82F6
Green#10B981
Purple#8B5CF6
Kratos#00377D
Luna#FFD100

Localization & Custom Strings

3 langues integrees. Remplacez n'importe quel texte via le parametre strings.

frFrancais
enEnglish
woWolof
Cles de chaines personnalisables
Ecran d'introduction
  • introTitle
  • introSubtitle
  • introStartButton
  • introDocTypeLabel
  • introDocTypePrefix
Capture Selfie
  • selfieStepTitle
  • selfiePlaceFace
  • selfieHoldStill
  • selfieFaceDetected
  • selfieNoFace
  • selfieTooFar
  • selfieTooClose
  • selfieNotCentered
Capture Document
  • stepRectoTitle
  • stepVersoTitle
  • capturePlaceCardInFrame
  • captureDocumentDetected
  • captureLookingForDocument
Defis
  • challengeCenterFace
  • challengeCloseEyes
  • challengeTurnLeft
  • challengeTurnRight
  • challengeSmile
  • challengeLookUp
  • challengeLookDown
Resultats
  • resultLivenessSuccess
  • resultLivenessFailed
  • resultScoreLabel
  • resultRetryPrompt
  • buttonContinue
  • buttonRetry
  • buttonCancel
Analyse
  • statusAnalyzing
  • analyzingPleaseWait
  • analysisStepImageCaptured
  • analysisStepQualityVerified
  • analysisStepSecurityCheck
  • analysisStepAIVerification

enum Types & Enums

CaptureStep
selfieVerification liveness du visage
rectoFace avant du document (OCR + detection de fraude)
versoFace arriere du document (NIN + MRZ)
ChallengeMode
minimal1-2 defis (plus rapide)
standard3-4 defis (equilibre, defaut)
strict5+ defis (securite maximale)
DisplayMode
standardCamera + section instructions en dessous
compactCamera + instructions superposees
immersiveCamera plein ecran, overlays flottants
Documents Supportes
SN-CINCarte d'identite senegalaise (recto + verso)
SN-PASSPORTPasseport senegalais (recto uniquement)
SN-DRIVER-LICENCEPermis de conduire senegalais (recto + verso)

result KYCResult

Retourne par initKyc() / startKyc()

Property Type Description
successboolKYC termine sans erreur
overallStatusVerificationStatuspass | review | reject | error
sessionIdString?ID de session serveur (pour la correlation webhook)
selfieResultSelfieResult?Resultat de la verification liveness
rectoResultDocumentResult?OCR face avant + analyse de fraude
versoResultDocumentResult?Face arriere (NIN, MRZ)
fraudIndicatorsString[]Signaux de fraude detectes
totalProcessingTimeMsintTemps de traitement total
errorMessageString?Description de l'erreur en cas d'echec
Getters de commodite :
selfieImagebytes?Image selfie capturee
rectoImagebytes?Image recto redressée
versoImagebytes?Image verso redressée
faceMatchesbool?Le selfie correspond a la photo du document
faceSimilarityScoredouble?Score de correspondance faciale (0-100%)
getExtractedValue(key)String?Obtenir n'importe quel champ OCR par cle
mainPhotoExtractedPhoto?Photo du visage extraite du document

result SelfieResult

PropertyTypeDescription
successboolEtape terminee avec succes
isLiveboolPersonne reelle detectee (pas une photo/ecran/masque)
confidencedoubleScore de confiance (0.0 - 1.0)
statusVerificationStatuspass ou reject
capturedImagebytes?Image selfie capturee (JPEG)
challengesPassedintNombre de defis reussis
challengesTotalintTotal des defis assignes
userMessagesString[]Messages localises pour l'utilisateur
spoofingIndicatorsString[]Signaux de spoofing detectes
processingTimeMsintTemps de traitement en ms

result DocumentResult

Utilise pour le recto et le verso

PropertyTypeDescription
successboolEtape terminee avec succes
isLiveboolDocument physique reel (pas un ecran/imprime)
scoredoubleScore d'authenticite (0.0 - 1.0)
confidenceLevelStringHIGH | MEDIUM | LOW
statusVerificationStatuspass | review | reject
alignedDocumentbytes?Image du document corrigee en perspective
extractionDocumentData?Champs extraits par OCR (voir ci-dessous)
extractedPhotosExtractedPhoto[]Photos de visage extraites du document
faceVerificationFaceResult?Correspondance selfie vs document
userMessagesString[]Messages localises pour l'utilisateur
fraudIndicatorsString[]Signaux de fraude detectes
processingTimeMsintTemps de traitement en ms

data ExtractedField & DocumentData

DocumentData contient une liste triee de ExtractedField. Utilisez getValue(key) ou iterez sur sortedFields.

PropertyTypeDescription
keyStringCle generique (document_id, first_name, birth_date...)
documentKeyStringCle specifique au document (numero_carte, prenoms...)
labelStringLibelle d'affichage localise
valueanyValeur extraite (String, tableau ou map)
displayPriorityintOrdre de tri (plus bas = affiche en premier)
iconString?Nom de l'icone (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é.

Methodes DocumentData

sortedFieldschamps tries par displayPriority (identifiant en premier)
getField(key) → ExtractedField? (par cle generique ou specifique)
getValue(key) → String? (valeur en tant que chaine)

data ExtractedPhoto

PropertyTypeDescription
imageBytesbytesPhoto du visage (JPEG)
confidencedoubleConfiance de detection (0-1)
widthintLargeur de l'image en px
heightintHauteur de l'image en px
bboxdouble[]Boite englobante [x1, y1, x2, y2]

result FaceResult

Correspondance selfie vs document (dans DocumentResult.faceVerification)

PropertyTypeDescription
isMatchboolLes visages correspondent (selfie = photo document)
similarityScoredoublePourcentage de similarite (0-100%)
thresholddoubleSeuil de correspondance utilise
confidenceLevelStringVERY_HIGH | HIGH | MEDIUM | LOW
detectionModelStringModele utilise (ex. : scrfd_10g)
recognitionModelStringModele utilise (ex. : buffalo_l)
processingTimeMsintTemps de comparaison des visages en 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

KYC Verification Playground

Testez l'API REST KYC — meme pipeline que le SDK en une seule requete HTTP.

POST /api/v1/kyc/verify

Face avant du document

Selectionnez les etapes, uploadez les images et lancez la verification

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

AML Verification Playground

Testez l'API de verification AML/Sanctions.

POST /api/v1/verify/aml

Remplissez le formulaire et lancez le screening AML

Face Identification Playground

Envoyez une photo de visage pour identifier la personne parmi les identites KYC existantes (recherche 1:N).

POST /api/v1/identify

Photo de visage a identifier

1 a 10, defaut 3

0.0 a 1.0, defaut 0.6

Uploadez une photo et cliquez sur Identifier

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
    },
    "aml_screening": {
      "status": "clear",
      "risk_level": "low",
      "total_matches": 0,
      "matches": [],
      "datasets_checked": ["us_ofac_sdn", "eu_fsf", "un_sc_sanctions", "uk_hmt_sanctions", "fr_tresor_gels", "interpol_red_notices", "pep_international", "custom_watchlist"],
      "screened_at": "2026-03-15T04:11:36Z",
      "processing_time_ms": 245
    }
  }
}

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

Screening AML / Sanctions

KyvShield verifie automatiquement les identites extraites contre les listes de sanctions internationales et les bases de personnes politiquement exposees (PEP). Aucune configuration supplementaire cote SDK n'est necessaire — le screening est execute automatiquement cote serveur lorsqu'il est active pour votre application.

Comment ca fonctionne

  1. 1 L'OCR extrait les donnees d'identite du document (nom, date de naissance, nationalite).
  2. 2 Les donnees sont automatiquement comparees (fuzzy matching) contre les listes OFAC, ONU, UE, UK et France.
  3. 3 Le resultat du screening est inclus dans la reponse KYC et dans le webhook session.completed.

Le screening est execute automatiquement — aucune modification du SDK ou des parametres d'appel n'est requise. Contactez-nous pour activer cette fonctionnalite sur votre application.

Sources de donnees

Sanctions

  • • OFAC SDN (USA)
  • Nations Unies
  • Union Europeenne
  • • UK Sanctions List
  • France (Gels d'avoirs)

PEP

  • Personnes politiquement exposees
  • Chefs d'Etat et de gouvernement
  • Hauts fonctionnaires

Conformite

  • • BCEAO
  • • CENTIF
  • Loi 02/2024 Senegal
  • Mise a jour quotidienne

aml_screening — objet dans la reponse

Cet objet est inclus dans final_result du webhook session.completed et dans la reponse de l'API REST POST /api/v1/kyc/verify.

Champ Type Description
statusstring"clear", "match", ou "error"
risk_levelstring"low", "medium", "high", "critical"
total_matchesintNombre total de correspondances trouvees
matchesarrayListe des correspondances trouvees (vide si aucune)
matches[].sourcestringSource de la correspondance ("ofac", "un", "eu", etc.)
matches[].name_matchedstringNom correspondant dans la liste
matches[].match_scorefloatScore de correspondance (0.0 a 1.0)
matches[].programsstring[]Programmes de sanctions (ex: SDGT, GLOBAL_TERRORISM)
matches[].listed_onstringDate d'ajout a la liste (ISO 8601)
datasets_checkedstring[]Listes consultees pour ce screening
screened_atstringHorodatage du screening (ISO 8601)
processing_time_msintTemps de traitement en millisecondes

Exemple — Aucune correspondance (clear)

{
  "aml_screening": {
    "status": "clear",
    "risk_level": "low",
    "total_matches": 0,
    "matches": [],
    "datasets_checked": ["us_ofac_sdn", "eu_fsf", "un_sc_sanctions", "uk_hmt_sanctions", "fr_tresor_gels", "interpol_red_notices", "pep_international", "custom_watchlist"],
    "screened_at": "2026-04-10T12:00:00Z",
    "processing_time_ms": 245
  }
}

Exemple — Correspondance trouvee (match)

{
  "aml_screening": {
    "status": "match",
    "risk_level": "high",
    "total_matches": 1,
    "matches": [
      {
        "source": "ofac",
        "source_id": "12345",
        "name_matched": "MOUSSA NDOUR",
        "match_score": 0.92,
        "programs": ["SDGT"],
        "listed_on": "2026-01-15"
      }
    ],
    "datasets_checked": ["us_ofac_sdn", "eu_fsf", "un_sc_sanctions", "uk_hmt_sanctions", "fr_tresor_gels", "interpol_red_notices", "pep_international", "custom_watchlist"],
    "screened_at": "2026-04-10T12:00:00Z",
    "processing_time_ms": 312
  }
}

Notes importantes

  • Le screening AML est per-session : passez require_aml=true dans le formulaire REST ou require_aml: true dans le handshake WS. L'application doit avoir allow_aml active dans ses parametres.
  • Le champ aml_screening est present uniquement dans session.completed et dans la reponse REST, pas dans les evenements de step.
  • Les listes de sanctions sont mises a jour quotidiennement.
  • Conformite aux exigences BCEAO et CENTIF (Loi 02/2024).