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 :
Puis demandez a Claude :
Voir le plugin sur GitHub — Fonctionne avec Claude Code
Flow KYC Complet
Installation
Choisissez votre plateforme
Source: GitHub
getUserMedia) ne fonctionne que sur https:// ou localhost. En production, servez toujours votre page en HTTPS.
Swift Package Manager
NSCameraUsageDescription dans votre Info.plist.
Source: GitHub
SDKs Serveur (API REST)
Verification KYC serveur-a-serveur, sans WebSocket
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
Installation
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
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)
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)
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
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 |
|---|---|---|
| selfie | minimal | selfie_center_face, selfie_close_eyes |
| selfie | standard | selfie_center_face, selfie_close_eyes, selfie_turn_left, selfie_turn_right |
| selfie | strict | + selfie_smile, selfie_look_up, selfie_look_down |
| recto/verso | minimal | recto_center_document |
| recto/verso | standard | recto_center_document, recto_tilt_left, recto_tilt_right |
| recto/verso | strict | + 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) |
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
https://kyvshield.innolink.sn
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) | KycResult | Lance le flux KYC complet. Retourne le resultat quand l'utilisateur termine ou annule. |
| checkCameraPermission() | bool | Verifie et demande la permission camera. Retourne true si accordee. |
| isCameraPermissionPermanentlyDenied() | bool | Retourne true si la camera a ete refusee definitivement. L'utilisateur doit aller dans Reglages. |
| openSettings() | bool | Ouvre les reglages du systeme pour que l'utilisateur active manuellement la camera. |
config KyvshieldConfig
| Property | Type | Default | Description |
|---|---|---|---|
| baseUrl | String | requis | URL du serveur API |
| apiKey | String | requis | Cle API du tableau de bord |
| apiVersion | String | 'v1' | Version de l'API |
| timeoutSeconds | int | 60 | Timeout HTTP (pas le timeout de session) |
| enableLog | bool | false | Activer les logs de debug |
| theme | ThemeConfig? | null | Theme personnalise (voir ci-dessous) |
| acceptReviewStatus | bool | false | Accepter le statut REVIEW comme valide |
flow KyvshieldFlowConfig
| Property | Type | Default | Description |
|---|---|---|---|
| steps | CaptureStep[] | [selfie, recto, verso] | Etapes a executer (l'ordre est important) |
| target | String? | null (auto-detect) | Type de document : SN-CIN, SN-PASSPORT, SN-DRIVER-LICENCE |
| challengeMode | ChallengeMode | standard | minimal (1) | standard (3) | strict (5+) |
| language | String | 'fr' | fr | en | wo |
| showIntroPage | bool | true | Afficher l'ecran d'introduction avant le KYC |
| showInstructionPages | bool | true | Afficher les instructions avant chaque etape |
| showResultPage | bool | true | Afficher l'ecran de resultat apres le KYC |
| showSuccessPerStep | bool | true | Afficher l'animation de succes apres chaque etape |
| requireFaceMatch | bool | true | Comparer le selfie avec la photo du document |
| selfieDisplayMode | DisplayMode | standard | standard | compact | immersive |
| documentDisplayMode | DisplayMode | standard | standard | compact | immersive |
| playChallengeAudio | bool | false | Jouer les instructions vocales pour chaque defi |
| maxChallengeAudioPlay | int | 1 | Repeter l'audio 1, 2 ou 3 fois |
| kycIdentifier | String? | null | Votre ID de reference (retourne dans les webhooks) |
| strings | Strings? | null | Remplacer 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 |
|---|---|---|
| primaryColor | Color / String | Couleur principale (boutons, accents, indicateurs) |
| successColor | Color? | Couleur de succes (defaut : #10B981) |
| warningColor | Color? | Couleur d'avertissement (defaut : #F59E0B) |
| errorColor | Color? | Couleur d'erreur (defaut : #EF4444) |
| themeMode | String | light | dark | auto |
Presets
#EF8352#3B82F6#10B981#8B5CF6#00377D#FFD100Localization & Custom Strings
3 langues integrees. Remplacez n'importe quel texte via le parametre strings.
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 visagerectoFace 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 dessouscompactCamera + instructions superposeesimmersiveCamera plein ecran, overlays flottantsDocuments 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 |
|---|---|---|
| success | bool | KYC termine sans erreur |
| overallStatus | VerificationStatus | pass | review | reject | error |
| sessionId | String? | ID de session serveur (pour la correlation webhook) |
| selfieResult | SelfieResult? | Resultat de la verification liveness |
| rectoResult | DocumentResult? | OCR face avant + analyse de fraude |
| versoResult | DocumentResult? | Face arriere (NIN, MRZ) |
| fraudIndicators | String[] | Signaux de fraude detectes |
| totalProcessingTimeMs | int | Temps de traitement total |
| errorMessage | String? | Description de l'erreur en cas d'echec |
| Getters de commodite : | ||
| selfieImage | bytes? | Image selfie capturee |
| rectoImage | bytes? | Image recto redressée |
| versoImage | bytes? | Image verso redressée |
| faceMatches | bool? | Le selfie correspond a la photo du document |
| faceSimilarityScore | double? | Score de correspondance faciale (0-100%) |
| getExtractedValue(key) | String? | Obtenir n'importe quel champ OCR par cle |
| mainPhoto | ExtractedPhoto? | Photo du visage extraite du document |
result SelfieResult
| Property | Type | Description |
|---|---|---|
| success | bool | Etape terminee avec succes |
| isLive | bool | Personne reelle detectee (pas une photo/ecran/masque) |
| confidence | double | Score de confiance (0.0 - 1.0) |
| status | VerificationStatus | pass ou reject |
| capturedImage | bytes? | Image selfie capturee (JPEG) |
| challengesPassed | int | Nombre de defis reussis |
| challengesTotal | int | Total des defis assignes |
| userMessages | String[] | Messages localises pour l'utilisateur |
| spoofingIndicators | String[] | Signaux de spoofing detectes |
| processingTimeMs | int | Temps de traitement en ms |
result DocumentResult
Utilise pour le recto et le verso
| Property | Type | Description |
|---|---|---|
| success | bool | Etape terminee avec succes |
| isLive | bool | Document physique reel (pas un ecran/imprime) |
| score | double | Score d'authenticite (0.0 - 1.0) |
| confidenceLevel | String | HIGH | MEDIUM | LOW |
| status | VerificationStatus | pass | review | reject |
| alignedDocument | bytes? | Image du document corrigee en perspective |
| extraction | DocumentData? | Champs extraits par OCR (voir ci-dessous) |
| extractedPhotos | ExtractedPhoto[] | Photos de visage extraites du document |
| faceVerification | FaceResult? | Correspondance selfie vs document |
| userMessages | String[] | Messages localises pour l'utilisateur |
| fraudIndicators | String[] | Signaux de fraude detectes |
| processingTimeMs | int | Temps de traitement en ms |
data ExtractedField & DocumentData
DocumentData contient une liste triee de ExtractedField. Utilisez getValue(key) ou iterez sur sortedFields.
| Property | Type | Description |
|---|---|---|
| key | String | Cle generique (document_id, first_name, birth_date...) |
| documentKey | String | Cle specifique au document (numero_carte, prenoms...) |
| label | String | Libelle d'affichage localise |
| value | any | Valeur extraite (String, tableau ou map) |
| displayPriority | int | Ordre de tri (plus bas = affiche en premier) |
| icon | String? | 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
data ExtractedPhoto
| Property | Type | Description |
|---|---|---|
| imageBytes | bytes | Photo du visage (JPEG) |
| confidence | double | Confiance de detection (0-1) |
| width | int | Largeur de l'image en px |
| height | int | Hauteur de l'image en px |
| bbox | double[] | Boite englobante [x1, y1, x2, y2] |
result FaceResult
Correspondance selfie vs document (dans DocumentResult.faceVerification)
| Property | Type | Description |
|---|---|---|
| isMatch | bool | Les visages correspondent (selfie = photo document) |
| similarityScore | double | Pourcentage de similarite (0-100%) |
| threshold | double | Seuil de correspondance utilise |
| confidenceLevel | String | VERY_HIGH | HIGH | MEDIUM | LOW |
| detectionModel | String | Modele utilise (ex. : scrfd_10g) |
| recognitionModel | String | Modele utilise (ex. : buffalo_l) |
| processingTimeMs | int | Temps 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_carte | N° Carte d'identite | X XXXX AAAA XXXXX | Oui |
| prenoms | Prenoms | Texte | Oui |
| nom | Nom | Texte | Oui |
| date_naissance | Date de naissance | JJ/MM/AAAA | Oui |
| sexe | Sexe | M / F | Oui |
| taille | Taille | X,XX m | Non |
| lieu_naissance | Lieu de naissance | Texte | Non |
| date_delivrance | Date de delivrance | JJ/MM/AAAA | Non |
| date_expiration | Date d'expiration | JJ/MM/AAAA | Oui |
| centre_enregistrement | Centre d'enregistrement | Texte | Non |
| adresse_domicile | Adresse du domicile | Texte | Non |
| birth_region | Region de naissance (derive) | Deduit | Auto |
| Cle | Label | Format | Requis |
|---|---|---|---|
| code_pays | Code Pays | SEN (fixe) | Oui |
| Informations electorales — presents si l'electeur est inscrit, sinon null | |||
| inscrit_liste_electorale | Inscrit sur la liste electorale | true / false | Non |
| numero_electeur | Numero d'electeur | chiffres | Non |
| region | Region | Texte | Non |
| departement | Departement | Texte | Non |
| arrondissement | Arrondissement | Texte | Non |
| commune | Commune | Texte | Non |
| lieu_de_vote | Lieu de vote | Texte | Non |
| bureau_de_vote | Bureau de vote | Numero | Non |
| nin | NIN (Numero d'Identification Nationale) | 13 chiffres sans espaces | Oui |
| mrz | MRZ (Machine Readable Zone) | 3 lignes x 30 chars | Oui |
Structure MRZ (ICAO 9303)
Passeport Biometrique
SN-PASSPORT
| Cle | Label | Format | Requis |
|---|---|---|---|
| numero_passeport | N° Passeport | AXXXXXXXX | Oui |
| nom | Nom | UPPERCASE | Oui |
| prenoms | Prenoms | UPPERCASE | Oui |
| nationalite | Nationalite | SENEGALAISE | Oui |
| date_naissance | Date de naissance | DD MMM YYYY | Oui |
| nin | N° personnel (NIN) | 13 chiffres | Oui |
| sexe | Sexe | M / F | Oui |
| lieu_naissance | Lieu de naissance | Texte | Oui |
| autorite | Autorite | Texte | Oui |
| date_delivrance | Date de delivrance | DD MMM YYYY | Oui |
| date_expiration | Date d'expiration | DD MMM YYYY | Oui |
| mrz | MRZ (Machine Readable Zone) | 2 lignes x 44 chars | Oui |
Structure MRZ Passeport (ICAO 9303 TD3)
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
| Cle | Label | Format | Requis |
|---|---|---|---|
| numero_permis | N° Permis | XXXXXXXX | Oui |
| nom | Nom | Texte | Oui |
| prenoms | Prenoms | Texte | Oui |
| date_naissance | Date et lieu de naissance | DD/MM/YYYY + Ville | Oui |
| date_emission | Date d'emission | DD/MM/YYYY | Oui |
| date_expiration | Date d'expiration | DD/MM/YYYY | Oui |
| delivre_par | Delivre par | MITTD | Oui |
| categories | Categories | A1 B etc | Oui |
| groupe_sanguin | Groupe sanguin | O+ A- B+ | Non |
| Cle | Label | Format | Requis |
|---|---|---|---|
| adresse | Adresse | Texte | Oui |
| commune | Commune | Texte | Oui |
| departement | Departement | Texte | Oui |
| region | Region | Texte | Oui |
| sexe | Sexe | M / F | Oui |
| categories_actives | Categories actives (derive) | ["A1", "B"] | Auto |
| mrz | MRZ | D1SN... | Oui |
Tableau des Categories (colonnes 9-12)
Structure MRZ Permis (1 ligne)
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
KYC Verification Playground
Testez l'API REST KYC — meme pipeline que le SDK en une seule requete HTTP.
Face avant du document
Analyse KYC en cours... (10-30 secondes)
Selfie liveness + OCR + Fraud detection + Face match
Reponse JSON brute
Selectionnez les etapes, uploadez les images et lancez la verification
Face Verification Playground
Testez l'API de verification faciale.
Visage de reference
Selfie ou photo
Verification en cours...
Uploadez deux images et cliquez sur Verifier
Face Detection Playground
Detectez les visages dans une image et obtenez les bounding boxes.
Glissez ou cliquez pour uploader
Detection en cours...
Uploadez une image et cliquez sur Detecter
AML Verification Playground
Testez l'API de verification AML/Sanctions.
Screening en cours...
Reponse JSON brute
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).
Photo de visage a identifier
1 a 10, defaut 3
0.0 a 1.0, defaut 0.6
Recherche en cours...
Reponse JSON brute
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.
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.
Chargement des documents...
Cliquez sur Charger (API Key requise ci-dessus)
Usage API
Consultez les statistiques d'utilisation de votre application avec suivi mensuel.
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)
Chargement des statistiques...
Cliquez sur Charger (API Key requise ci-dessus)
Compteurs disponibles
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.
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...
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))
}
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 L'OCR extrait les donnees d'identite du document (nom, date de naissance, nationalite).
- 2 Les donnees sont automatiquement comparees (fuzzy matching) contre les listes OFAC, ONU, UE, UK et France.
-
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 |
|---|---|---|
| status | string | "clear", "match", ou "error" |
| risk_level | string | "low", "medium", "high", "critical" |
| total_matches | int | Nombre total de correspondances trouvees |
| matches | array | Liste des correspondances trouvees (vide si aucune) |
| matches[].source | string | Source de la correspondance ("ofac", "un", "eu", etc.) |
| matches[].name_matched | string | Nom correspondant dans la liste |
| matches[].match_score | float | Score de correspondance (0.0 a 1.0) |
| matches[].programs | string[] | Programmes de sanctions (ex: SDGT, GLOBAL_TERRORISM) |
| matches[].listed_on | string | Date d'ajout a la liste (ISO 8601) |
| datasets_checked | string[] | Listes consultees pour ce screening |
| screened_at | string | Horodatage du screening (ISO 8601) |
| processing_time_ms | int | Temps 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=truedans le formulaire REST ourequire_aml: truedans le handshake WS. L'application doit avoirallow_amlactive dans ses parametres. - • Le champ
aml_screeningest present uniquement danssession.completedet 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).