Skip to content

YeboVerify — Product Requirements Document

B2B Identity Verification API for Africa Version 1.0 | March 2026


Table of Contents

  1. Executive Summary
  2. Vision & Mission
  3. Problem Statement
  4. Solution
  5. Features
  6. User Journeys
  7. Data Models
  8. API Reference
  9. Service Architecture
  10. SDKs
  11. Authentication
  12. Billing & Plans
  13. Technical Stack
  14. Security
  15. Gaps & Missing Features

1. Executive Summary

YeboVerify is a B2B identity verification API designed for African businesses. It provides automated KYC (Know Your Customer) verification by comparing a user's selfie against their government-issued ID document and extracting personal information via OCR.

Key Capabilities

  • Face Comparison: AWS Rekognition-powered face matching between ID document photo and live selfie
  • Document OCR: Google Gemini AI extraction of personal data from ID documents
  • Multi-Platform SDKs: Node.js, React, React Native, Flutter, Kotlin (Android), Swift (iOS)
  • Async Processing: Non-blocking verification with webhook notifications
  • Multi-Tenant: API key authentication for business accounts

Primary Use Cases

  • Customer onboarding for fintech/banking apps
  • Age/identity verification for e-commerce
  • Employee identity verification for HR platforms
  • Compliance with KYC regulations

2. Vision & Mission

Vision

Become the leading identity verification infrastructure for Africa, enabling businesses to onboard customers securely and compliantly with world-class technology.

Mission

Provide simple, fast, and accurate identity verification that works with African ID documents, at a price point accessible to startups and enterprises alike.

Guiding Principles

  1. Security First: Documents encrypted at rest and in transit; never stored publicly
  2. Developer Experience: Simple integration via RESTful API and native SDKs
  3. African Focus: Optimized for African ID document formats (National IDs, passports)
  4. Speed: Sub-5-second verification times with async webhooks

3. Problem Statement

The Challenge

African businesses face significant hurdles implementing KYC:

  1. Global providers are expensive — per-verification pricing often exceeds $1-2 USD, prohibitive for high-volume African businesses
  2. Poor support for African documents — OCR models trained on Western documents fail on African ID formats
  3. Complex integration — most providers require weeks of integration work
  4. No local data residency — compliance concerns with data leaving the continent

Market Gap

Existing solutions (Jumio, Onfido, Veriff) are priced for Western markets and lack optimization for African ID documents. Local alternatives are fragmented and lack modern developer tooling.

Target Customers

SegmentUse CaseVolume
Fintech startupsUser onboarding100-10,000/month
BanksAccount opening10,000-100,000/month
E-commerceAge verification1,000-50,000/month
HR platformsEmployee verification100-5,000/month
GovernmentCitizen services50,000+/month

4. Solution

Overview

YeboVerify provides a single API endpoint that accepts:

  1. ID Front Image — photo of the front of government-issued ID
  2. Selfie — live photo of the person
  3. ID Back Image (optional) — back of ID for additional data

Within seconds, YeboVerify:

  1. Compares the face on the ID to the selfie using AWS Rekognition
  2. Extracts personal data (name, DOB, ID number) via Gemini OCR
  3. Returns a decision (APPROVED / REJECTED / NEEDS_REVIEW)
  4. Sends results to configured webhook

Architecture Overview

┌─────────────┐     ┌──────────────────┐     ┌─────────────────┐
│   Client    │────▶│  YeboVerify API  │────▶│ Cloudflare R2   │
│   (SDK)     │     │  (Cloud Run)     │     │ (Storage)       │
└─────────────┘     └────────┬─────────┘     └─────────────────┘

        ┌────────────────────┼────────────────────┐
        │                    │                    │
        ▼                    ▼                    ▼
┌───────────────┐   ┌────────────────┐   ┌───────────────┐
│ AWS Rekognition│   │ Google Gemini  │   │ PostgreSQL    │
│ (Face Match)  │   │ (OCR)          │   │ (Neon)        │
└───────────────┘   └────────────────┘   └───────────────┘

5. Features

5.1 Core Features (Implemented)

FeatureDescriptionStatus
Face ComparisonAWS Rekognition CompareFaces API✅ Live
Document OCRGemini 2.5 Flash multimodal extraction✅ Live
Async ProcessingBackground processing with setImmediate✅ Live
Webhook NotificationsHMAC-signed POST to business webhook✅ Live
Multi-tenancyAPI key per business account✅ Live
Business DashboardStats endpoint with verification counts✅ Live
Verification HistoryPaginated list with status filtering✅ Live
Image StorageCloudflare R2 (private, presigned URLs)✅ Live

5.2 Decision Logic

The verification decision is computed based on face match score and OCR confidence:

Face ScoreOCR ConfidenceDecisionConfidence Level
≥ 85%≥ 70%APPROVEDHigh
≥ 80%≥ 60%APPROVEDMedium
≥ 70%≥ 50%NEEDS_REVIEWMedium
< 60%(skipped)REJECTEDLow
< 70%< 50%REJECTEDLow

5.3 OCR Extracted Fields

FieldDescriptionExample
surnameFamily/last nameDLAMINI
namesGiven namesSIBUSISO JOHN
dateOfBirthDOB in YYYY-MM-DD1990-05-15
idNumberNational ID number9005155555083
sexGenderMale / Female
chiefCodeChief/regional code12345
issueDateDocument issue date2020-01-15
expiryDateDocument expiry date2030-01-15
documentTypeType of documentNational ID

5.4 Supported File Types

  • image/jpeg
  • image/png
  • image/webp
  • image/heic
  • image/heif

Max file size: 10MB per image


6. User Journeys

6.1 Business Integrator Journey

Persona: Developer at a fintech company implementing KYC

Steps

  1. Registration

    • Admin creates business account via admin API
    • Receives API key (yv_live_xxx...)
  2. Integration

    • Installs SDK for their platform (Node/React/Flutter/etc.)
    • Configures webhook URL for async results
  3. Testing

    • Submits test verifications
    • Verifies webhook signatures
  4. Production

    • Deploys SDK widget to production app
    • Monitors verification stats via /v1/account
javascript
// Node.js Integration Example
import { YeboVerify } from '@yeboverify/node';

const client = new YeboVerify({ apiKey: 'yv_live_xxx' });

// Configure webhook
await client.setWebhook('https://api.myapp.com/webhooks/kyc');

// Submit verification
const { verificationId } = await client.verify({
  idFront: '/path/to/id.jpg',
  selfie: '/path/to/selfie.jpg',
  externalRef: 'user_123',
});

// Results arrive via webhook...

6.2 End User Journey

Persona: Customer signing up for a fintech app

Steps

  1. Prompt

    • App displays "Verify your identity" screen
    • YeboVerify widget appears
  2. ID Front

    • User photographs front of their National ID
    • Widget validates image quality
  3. ID Back (optional)

    • User photographs back of ID
    • Can skip if not required
  4. Selfie

    • User takes a selfie facing camera
    • Instructions: good lighting, no glasses
  5. Review

    • User reviews all captured photos
    • Can retake any photo
  6. Submit

    • Widget uploads to YeboVerify API
    • User sees "Processing..." state
  7. Result

    • App receives webhook notification
    • User sees approval or rejection

6.3 Admin Journey

Persona: YeboVerify platform administrator

Steps

  1. Create Business

    bash
    POST /v1/businesses/register
    Authorization: Bearer ADMIN_SECRET
  2. Monitor

    • View business verification stats
    • Review flagged verifications (NEEDS_REVIEW)
  3. Support

    • Lookup verification by ID
    • Investigate failed verifications

7. Data Models

7.1 Business Model

Represents a registered business customer.

prisma
model Business {
  id            String         @id @default(cuid())
  name          String                              // Company name
  email         String         @unique              // Contact email
  apiKey        String         @unique @default(cuid())  // API key (yv_live_xxx)
  webhookUrl    String?                             // Webhook endpoint URL
  webhookSecret String?                             // HMAC signing secret (whsec_xxx)
  active        Boolean        @default(true)       // Account active/disabled
  plan          String         @default("free")     // Subscription plan
  createdAt     DateTime       @default(now())
  updatedAt     DateTime       @updatedAt
  verifications Verification[]
}
FieldTypeDescription
idcuidPrimary key
nameStringBusiness display name
emailStringUnique contact email
apiKeyStringAPI key (format: yv_live_<32chars>)
webhookUrlString?HTTPS URL for webhook delivery
webhookSecretString?HMAC secret (format: whsec_<32chars>)
activeBooleanAccount status
planStringPlan tier: free, basic, pro, enterprise
createdAtDateTimeAccount creation timestamp
updatedAtDateTimeLast update timestamp

7.2 Verification Model

Represents a single identity verification request.

prisma
model Verification {
  id             String             @id @default(cuid())
  verificationId String             @unique         // Public ID (vrf_xxx)
  businessId     String                             // FK to Business
  externalRef    String?                            // Client's reference ID
  status         VerificationStatus @default(PENDING)
  idFrontImage   String                             // R2 storage key
  idBackImage    String?                            // R2 storage key (optional)
  selfieImage    String                             // R2 storage key
  extractedData  Json?                              // OCR results
  ocrConfidence  Float?                             // 0-100 OCR confidence
  faceScore      Float?                             // 0-100 face similarity
  faceDecision   String?                            // approved/rejected/needs_review
  decision       Decision?                          // Final decision
  decisionReason String?                            // Human-readable reason
  confidence     String?                            // high/medium/low
  processingMs   Int?                               // Processing time in ms
  webhookSent    Boolean            @default(false)
  webhookSentAt  DateTime?
  webhookError   String?
  createdAt      DateTime           @default(now())
  updatedAt      DateTime           @updatedAt
  completedAt    DateTime?
  business       Business           @relation(...)

  @@index([businessId])
  @@index([status])
}
FieldTypeDescription
idcuidInternal database ID
verificationIdStringPublic ID (format: vrf_<16chars>)
businessIdStringForeign key to Business
externalRefString?Client's user/order ID
statusVerificationStatusProcessing status
idFrontImageStringR2 object key for ID front
idBackImageString?R2 object key for ID back
selfieImageStringR2 object key for selfie
extractedDataJson?Structured OCR output
ocrConfidenceFloat?OCR confidence percentage (0-100)
faceScoreFloat?Face similarity percentage (0-100)
faceDecisionString?Face comparison decision
decisionDecision?Final verification decision
decisionReasonString?Explanation of decision
confidenceString?Confidence level: high, medium, low
processingMsInt?Total processing time
webhookSentBooleanWebhook delivery status
webhookSentAtDateTime?Webhook delivery timestamp
webhookErrorString?Webhook error message if failed
createdAtDateTimeVerification creation time
updatedAtDateTimeLast update time
completedAtDateTime?Processing completion time

7.3 Enums

VerificationStatus

typescript
enum VerificationStatus {
  PENDING       // Just submitted, not yet processing
  PROCESSING    // Currently being verified
  COMPLETED     // Successfully processed
  FAILED        // Processing failed (error)
  NEEDS_REVIEW  // Requires manual review
}

Decision

typescript
enum Decision {
  APPROVED      // Identity verified successfully
  REJECTED      // Identity verification failed
  NEEDS_REVIEW  // Manual review required
}

8. API Reference

Base URL

https://api.yeboverify.com

8.1 Health Check

GET /health

Returns API health status.

Response

json
{
  "status": "ok",
  "timestamp": "2026-03-19T10:00:00.000Z",
  "service": "yeboverify-api",
  "version": "1.0.0"
}

8.2 Admin Endpoints

POST /v1/businesses/register

Create a new business account.

Authentication: Bearer token (ADMIN_SECRET)

Request Body

json
{
  "name": "Acme Corp",
  "email": "admin@acme.com",
  "plan": "pro"
}
FieldTypeRequiredDescription
namestringBusiness name (1-255 chars)
emailstringValid email address
planstringfree, basic, pro, enterprise (default: free)

Response (201 Created)

json
{
  "success": true,
  "data": {
    "businessId": "clx...",
    "apiKey": "yv_live_aBcDeFgHiJkLmNoPqRsTuVwXyZ123456",
    "name": "Acme Corp",
    "email": "admin@acme.com",
    "plan": "pro"
  }
}

8.3 Business Account Endpoints

All endpoints require X-API-Key header.

GET /v1/account

Get current business account info and stats.

Response

json
{
  "success": true,
  "data": {
    "id": "clx...",
    "name": "Acme Corp",
    "email": "admin@acme.com",
    "webhookUrl": "https://api.acme.com/webhooks/kyc",
    "plan": "pro",
    "createdAt": "2026-01-01T00:00:00.000Z",
    "stats": {
      "total": 1234,
      "pending": 5,
      "completed": 1200,
      "approved": 1100,
      "rejected": 80,
      "needsReview": 20
    }
  }
}

PUT /v1/account/webhook

Configure webhook URL for receiving verification results.

Request Body

json
{
  "webhookUrl": "https://api.acme.com/webhooks/kyc",
  "webhookSecret": "my-custom-secret"
}
FieldTypeRequiredDescription
webhookUrlstringValid HTTPS URL
webhookSecretstringCustom secret (auto-generated if omitted)

Response

json
{
  "success": true,
  "data": {
    "webhookUrl": "https://api.acme.com/webhooks/kyc",
    "webhookSecret": "whsec_aBcDeFgHiJkLmNoPqRsTuVwXyZ123456",
    "message": "Webhook configured successfully"
  }
}

8.4 Verification Endpoints

POST /v1/verify

Submit a new identity verification.

Content-Type: multipart/form-data

Form Fields

FieldTypeRequiredDescription
idFrontfileFront of ID document (JPEG, PNG, WebP, HEIC)
selfiefileUser's selfie
idBackfileBack of ID document
externalRefstringYour reference ID (e.g., user ID)
documentTypestringDocument type hint

Response (202 Accepted)

json
{
  "success": true,
  "data": {
    "verificationId": "vrf_abc123xyz456789",
    "status": "pending",
    "message": "Verification submitted successfully. Results will be sent via webhook."
  }
}

GET /v1/verifications/:verificationId

Get a specific verification by ID.

Response

json
{
  "success": true,
  "data": {
    "verificationId": "vrf_abc123xyz456789",
    "externalRef": "user_123",
    "status": "COMPLETED",
    "decision": "APPROVED",
    "decisionReason": "High face match and OCR confidence",
    "confidence": "high",
    "faceScore": 92.5,
    "ocrConfidence": 85.0,
    "extractedData": {
      "surname": "DLAMINI",
      "names": "SIBUSISO JOHN",
      "dateOfBirth": "1990-05-15",
      "idNumber": "9005155555083",
      "documentType": "National ID"
    },
    "processingMs": 3200,
    "createdAt": "2026-03-19T10:00:00.000Z",
    "completedAt": "2026-03-19T10:00:03.200Z"
  }
}

GET /v1/verifications

List verifications with pagination and filtering.

Query Parameters

ParameterTypeDefaultDescription
pageint1Page number
limitint20Items per page (max 100)
statusstring-Filter by status

Response

json
{
  "success": true,
  "data": {
    "verifications": [
      {
        "verificationId": "vrf_abc123xyz456789",
        "externalRef": "user_123",
        "status": "COMPLETED",
        "decision": "APPROVED",
        "confidence": "high",
        "createdAt": "2026-03-19T10:00:00.000Z",
        "completedAt": "2026-03-19T10:00:03.200Z"
      }
    ],
    "pagination": {
      "page": 1,
      "limit": 20,
      "total": 1234,
      "totalPages": 62
    }
  }
}

8.5 Webhook Payload

When verification completes, YeboVerify POSTs to your webhook URL:

json
{
  "event": "verification.completed",
  "verificationId": "vrf_abc123xyz456789",
  "externalRef": "user_123",
  "status": "completed",
  "decision": "approved",
  "confidence": "high",
  "faceScore": 92.5,
  "ocrConfidence": 85.0,
  "extractedData": {
    "surname": "DLAMINI",
    "names": "SIBUSISO JOHN",
    "dateOfBirth": "1990-05-15",
    "idNumber": "9005155555083",
    "documentType": "National ID"
  },
  "timestamp": "2026-03-19T10:00:03.200Z"
}

Headers

HeaderDescription
Content-Typeapplication/json
X-YeboVerify-SignatureHMAC-SHA256 signature

Signature Verification

javascript
const crypto = require('crypto');

function verifySignature(rawBody, signature, secret) {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(rawBody)
    .digest('hex');
  
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

8.6 Error Responses

All errors follow this format:

json
{
  "success": false,
  "error": {
    "code": "ERROR_CODE",
    "message": "Human readable error message"
  }
}
CodeHTTP StatusDescription
MISSING_API_KEY401X-API-Key header not provided
INVALID_API_KEY401API key not found or invalid
ACCOUNT_DISABLED403Business account is disabled
MISSING_AUTH401Admin Bearer token required
INVALID_TOKEN401Invalid admin token
MISSING_FILES400No files uploaded
MISSING_ID_FRONT400ID front image required
MISSING_SELFIE400Selfie image required
INVALID_FILE_TYPE400Unsupported file type
FILE_TOO_LARGE400File exceeds 10MB limit
NOT_FOUND404Resource not found
VALIDATION_ERROR400Request validation failed
EMAIL_EXISTS409Email already registered
VERIFICATION_ERROR500Verification processing error
INTERNAL_ERROR500Unexpected server error

9. Service Architecture

9.1 Face Comparison Service (AWS Rekognition)

File: src/services/aws-rekognition.service.ts

Uses AWS Rekognition CompareFaces API to match the face on an ID document with a selfie.

Configuration

env
AWS_ACCESS_KEY_ID=AKIA...
AWS_SECRET_ACCESS_KEY=...
AWS_REGION=us-east-1

Key Methods

MethodDescription
compareFaces(sourceBuffer, targetBuffer)Compare two face images
getVerificationDecision(result)Convert score to decision
isEnabled()Check if AWS credentials are configured

Face Comparison Result

typescript
interface FaceComparisonResult {
  similarity: number;           // 0-100 match score
  confidence: number;           // Source face detection confidence
  sourceImageFace: {
    boundingBox: { width, height, left, top };
    confidence: number;
  };
  faceMatches: Array<{
    similarity: number;
    face: { boundingBox, confidence };
  }>;
  unmatchedFaces: unknown[];
}

Decision Thresholds

SimilarityDecisionConfidence
≥ 90%approvedhigh
≥ 80%needs_reviewmedium
≥ 70%needs_reviewlow
< 70%rejectedlow

9.2 OCR Service (Google Gemini)

File: src/services/gemini-ocr.service.ts

Uses Google Gemini 2.5 Flash multimodal model to extract personal information from ID document images.

Configuration

env
GEMINI_API_KEY=AIzaSy...

Key Methods

MethodDescription
extractPersonalInfo(imageBuffer)Extract data from ID image
validatePersonalInfo(info)Validate extracted data completeness
isEnabled()Check if API key is configured

OCR Result

typescript
interface OCRResult {
  personalInfo: {
    surname?: string;
    names?: string;
    dateOfBirth?: string;
    sex?: string;
    idNumber?: string;
    chiefCode?: string;
    issueDate?: string;
    expiryDate?: string;
    documentType?: string;
    confidence: number;
  };
  extractedText: string[];
  confidence: number;
  success: boolean;
  error?: string;
}

Prompt Engineering

The service uses a structured prompt requesting JSON output:

Analyze this ID document image and extract the following personal information in JSON format.
Be very precise and only extract information that is clearly visible and readable.
...
Return ONLY a JSON object with the fields above.

9.3 Storage Service (Cloudflare R2)

File: src/services/storage.service.ts

Uses Cloudflare R2 (S3-compatible) for storing document images. Images are stored privately — never exposed publicly.

Configuration

env
R2_ACCOUNT_ID=9f15f12d867a59b212ed2ae3cf4615ca
R2_BUCKET=yeboverify-documents
R2_ACCESS_KEY_ID=...
R2_SECRET_ACCESS_KEY=...

Key Methods

MethodDescription
uploadFile(buffer, key, mimeType)Upload file to R2
downloadBuffer(key)Download file as Buffer
getSignedUrl(key, expiresIn)Generate presigned URL (15 min default)
deleteFile(key)Delete file from R2

Storage Structure

yeboverify-documents/
├── verifications/
│   ├── vrf_abc123xyz/
│   │   ├── id-front-1710844800000
│   │   ├── id-back-1710844800001
│   │   └── selfie-1710844800002
│   └── vrf_def456uvw/
│       └── ...

9.4 Webhook Service

File: src/services/webhook.service.ts

Handles webhook delivery with retry logic and HMAC signing.

Key Features

  • Retry: 3 attempts with exponential backoff (1s, 2s, 3s)
  • Signing: HMAC-SHA256 signature in X-YeboVerify-Signature header
  • Tracking: Records delivery status and errors in database

Webhook Payload

typescript
interface WebhookPayload {
  event: 'verification.completed';
  verificationId: string;
  externalRef: string | null;
  status: 'completed' | 'failed' | 'needs_review';
  decision: 'approved' | 'rejected' | 'needs_review';
  confidence: 'high' | 'medium' | 'low';
  faceScore: number;
  ocrConfidence: number;
  extractedData: {
    surname?: string;
    names?: string;
    dateOfBirth?: string;
    idNumber?: string;
    documentType?: string;
  };
  timestamp: string;
}

9.5 Verification Flow

1. Client submits POST /v1/verify with images


2. Upload images to R2 (private bucket)


3. Create Verification record (status: PENDING)


4. Return 202 Accepted with verificationId


5. Background: setImmediate → processVerification()

                    ├── Update status to PROCESSING

                    ├── Download images from R2

                    ├── AWS Rekognition: compareFaces()
                    │   │
                    │   ├── If faceScore < 60%: REJECT (skip OCR)
                    │   │
                    │   └── Continue...

                    ├── Gemini OCR: extractPersonalInfo()

                    ├── Calculate final decision

                    ├── Update Verification record

                    └── Send webhook (if configured)

10. SDKs

YeboVerify provides official SDKs for all major platforms.

10.1 Node.js SDK

Package: @yeboverify/node

Installation

bash
npm install @yeboverify/node

Usage

typescript
import { YeboVerify } from '@yeboverify/node';

const client = new YeboVerify({ apiKey: 'yv_live_xxx' });

// Submit verification
const { verificationId } = await client.verify({
  idFront: '/path/to/id.jpg',     // File path, Buffer, or base64
  selfie: '/path/to/selfie.jpg',
  externalRef: 'user_123',
});

// Get verification
const verification = await client.getVerification(verificationId);

// Poll until complete (for testing)
const result = await client.pollVerification(verificationId, {
  intervalMs: 2000,
  timeoutMs: 60000,
});

// Verify webhook signature
const isValid = client.verifyWebhook(rawBody, signature, webhookSecret);

Types

typescript
interface YeboVerifyConfig {
  apiKey: string;
  baseUrl?: string;  // defaults to https://api.yeboverify.com
}

interface VerifyOptions {
  idFront: Buffer | string;
  selfie: Buffer | string;
  idBack?: Buffer | string;
  externalRef?: string;
  documentType?: string;
}

interface Verification {
  verificationId: string;
  externalRef?: string | null;
  status: VerificationStatus;
  decision?: VerificationDecision | null;
  decisionReason?: string | null;
  confidence?: VerificationConfidence | null;
  faceScore?: number | null;
  ocrConfidence?: number | null;
  extractedData?: ExtractedData | null;
  processingMs?: number | null;
  createdAt: string;
  completedAt?: string | null;
}

10.2 React SDK

Package: @yeboverify/react

Installation

bash
npm install @yeboverify/react

Usage

tsx
import { YeboVerifyWidget } from '@yeboverify/react';

function KYCPage() {
  return (
    <YeboVerifyWidget
      apiKey="yv_live_xxx"
      externalRef={currentUser.id}
      documentType="National ID"
      requireIdBack={false}
      primaryColor="#16a34a"
      onSubmit={(verificationId) => {
        console.log('Submitted:', verificationId);
      }}
      onError={(error) => {
        console.error('Error:', error);
      }}
    />
  );
}

Props

PropTypeRequiredDescription
apiKeystringYour YeboVerify API key
baseUrlstringAPI base URL
externalRefstringYour reference ID
documentTypestringDocument type hint
requireIdBackbooleanRequire back of ID
primaryColorstringBrand color (default: #16a34a)
onSubmitfunctionCalled on successful submission
onResultfunctionCalled when result received
onErrorfunctionCalled on error
classNamestringCustom CSS class

10.3 React Native SDK

Package: @yeboverify/react-native

Installation

bash
npm install @yeboverify/react-native
npx expo install expo-image-picker

Usage

tsx
import { YeboVerifyScreen } from '@yeboverify/react-native';

function KYCScreen() {
  return (
    <YeboVerifyScreen
      apiKey="yv_live_xxx"
      externalRef={user.id}
      documentType="National ID"
      requireIdBack={false}
      primaryColor="#16a34a"
      onSubmit={(verificationId) => {
        navigation.navigate('KYCComplete', { verificationId });
      }}
      onError={(error) => {
        Alert.alert('Error', error.message);
      }}
    />
  );
}

10.4 Flutter SDK

Package: yeboverify

Installation

yaml
# pubspec.yaml
dependencies:
  yeboverify: ^1.0.0
  image_picker: ^1.0.0

Usage

dart
import 'package:yeboverify/yeboverify.dart';

// Programmatic API
final client = YeboVerify(apiKey: 'yv_live_xxx');

final verificationId = await client.verify(
  idFront: File('/path/to/id.jpg'),
  selfie: File('/path/to/selfie.jpg'),
  externalRef: user.id,
);

// Widget
YeboVerifyWidget(
  apiKey: 'yv_live_xxx',
  externalRef: user.id,
  requireIdBack: false,
  primaryColor: Color(0xFF16a34a),
  onSubmit: (verificationId) {
    Navigator.pushNamed(context, '/kyc-complete');
  },
  onError: (error) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(content: Text(error.message)),
    );
  },
)

Models

dart
enum VerificationStatus { pending, processing, completed, failed, needsReview }
enum VerificationDecision { approved, rejected, needsReview }
enum VerificationConfidence { high, medium, low }

class Verification {
  final String verificationId;
  final String? externalRef;
  final VerificationStatus status;
  final VerificationDecision? decision;
  final String? decisionReason;
  final VerificationConfidence? confidence;
  final double? faceScore;
  final double? ocrConfidence;
  final ExtractedData? extractedData;
  final int? processingMs;
  final DateTime createdAt;
  final DateTime? completedAt;
  
  bool get isApproved => decision == VerificationDecision.approved;
  bool get isPending => status == VerificationStatus.pending || status == VerificationStatus.processing;
}

10.5 Kotlin SDK (Android)

Package: com.yeboverify

Installation

gradle
// build.gradle
dependencies {
    implementation 'com.yeboverify:yeboverify:1.0.0'
    implementation 'com.squareup.okhttp3:okhttp:4.12.0'
}

Usage

kotlin
// Programmatic API
val client = YeboVerify(apiKey = "yv_live_xxx")

val verificationId = client.verify(
    idFront = File("/path/to/id.jpg"),
    selfie = File("/path/to/selfie.jpg"),
    externalRef = user.id,
)

// Launch built-in UI
val intent = client.launchVerificationFlow(
    context = this,
    externalRef = user.id,
    documentType = "National ID",
    requireIdBack = false,
)
startActivityForResult(intent, REQUEST_CODE_VERIFY)

// Handle result
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    if (requestCode == REQUEST_CODE_VERIFY && resultCode == RESULT_OK) {
        val verificationId = data?.getStringExtra(YeboVerifyActivity.EXTRA_VERIFICATION_ID)
        // Handle success
    }
}

10.6 Swift SDK (iOS)

Package: YeboVerify (Swift Package)

Installation

swift
// Package.swift
dependencies: [
    .package(url: "https://github.com/yeboverify/swift-sdk.git", from: "1.0.0")
]

Usage

swift
// Programmatic API
let client = YeboVerify(apiKey: "yv_live_xxx")

let verificationId = try await client.verify(
    idFront: idFrontImageData,
    selfie: selfieImageData,
    externalRef: user.id
)

// SwiftUI Widget
YeboVerifyView(
    apiKey: "yv_live_xxx",
    externalRef: user.id,
    documentType: "National ID",
    requireIdBack: false,
    primaryColor: Color.green
) { verificationId in
    print("Submitted:", verificationId)
} onError: { error in
    print("Error:", error.message)
}

11. Authentication

11.1 Business API Key Authentication

All verification and account endpoints use API key authentication:

X-API-Key: yv_live_aBcDeFgHiJkLmNoPqRsTuVwXyZ123456

API Key Format: yv_live_<32 alphanumeric characters>

Generated by: customAlphabet('0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', 32)

11.2 Admin Bearer Token

Admin endpoints (business registration) use Bearer token:

Authorization: Bearer YOUR_ADMIN_SECRET

Configured via: ADMIN_SECRET environment variable

11.3 Webhook Signature

Webhooks are signed with HMAC-SHA256:

X-YeboVerify-Signature: sha256=abc123...

Secret Format: whsec_<32 alphanumeric characters>


12. Billing & Plans

12.1 Current Plan Structure

PlanDescriptionDefault
freeTrial tier
basicStarter businesses
proGrowing businesses
enterpriseHigh-volume

Note: Billing/payments are not yet implemented. Plans are stored but not enforced.

12.2 Planned Pricing Model

PlanVerifications/monthPrice/verificationFeatures
Free100$0Basic OCR
Basic1,000$0.30+ Webhooks
Pro10,000$0.20+ Priority processing
EnterpriseUnlimitedCustom+ SLA, support

13. Technical Stack

13.1 Backend

ComponentTechnology
RuntimeNode.js 22+
LanguageTypeScript 5.7
FrameworkExpress.js 4.21
DatabasePostgreSQL (Neon serverless)
ORMPrisma 6.3
ValidationZod 3.24
LoggingWinston 3.17

13.2 External Services

ServiceProviderPurpose
Face RecognitionAWS RekognitionFace comparison
OCRGoogle Gemini 2.5 FlashDocument data extraction
StorageCloudflare R2Image storage
DatabaseNeonPostgreSQL
HostingGoogle Cloud RunContainer hosting
CI/CDGoogle Cloud BuildBuild & deploy

13.3 Dependencies

json
{
  "dependencies": {
    "@aws-sdk/client-rekognition": "^3.637.0",
    "@aws-sdk/client-s3": "^3.1006.0",
    "@aws-sdk/s3-request-presigner": "^3.1006.0",
    "@google/generative-ai": "^0.21.0",
    "@prisma/client": "^6.3.1",
    "cors": "^2.8.5",
    "dotenv": "^16.4.5",
    "express": "^4.21.1",
    "multer": "^1.4.5-lts.1",
    "nanoid": "^3.3.7",
    "winston": "^3.17.0",
    "zod": "^3.24.1"
  }
}

13.4 Deployment

  • Region: europe-west1 (Belgium)
  • Platform: Google Cloud Run
  • Build: Cloud Build with Docker
  • Deploy Script: deploy.sh (required for secrets)

14. Security

14.1 Data Protection

MeasureImplementation
Encryption at RestR2 server-side encryption
Encryption in TransitTLS 1.3
No Public URLsImages only accessible via presigned URLs (15 min expiry)
API Key HashingKeys stored as plain text (TODO: hash)
Webhook SigningHMAC-SHA256 with constant-time comparison

14.2 Access Control

LayerControl
Admin APIsBearer token (ADMIN_SECRET)
Business APIsAPI key (X-API-Key)
WebhooksHMAC signature verification
StoragePrivate bucket + presigned URLs

14.3 Input Validation

  • File types: Allowlist (JPEG, PNG, WebP, HEIC, HEIF)
  • File size: Max 10MB per image
  • Request body: Zod schema validation
  • ID format: Regex validation

15. Gaps & Missing Features

15.1 Critical Gaps

GapPriorityDescription
No rate limiting🔴 HighAPI has no rate limits — vulnerable to abuse
No billing/payments🔴 HighPlans exist but not enforced
No Stripe integration🔴 HighNo payment processing
No API key rotation🔴 HighCannot rotate compromised keys
API keys in plaintext🔴 HighShould be hashed

15.2 Important Missing Features

FeaturePriorityDescription
Admin dashboard🟡 MediumWeb UI for managing businesses
Manual review UI🟡 MediumInterface for reviewing NEEDS_REVIEW cases
Usage analytics🟡 MediumDetailed verification metrics
Document type detection🟡 MediumAuto-detect ID type (passport vs national ID)
Liveness detection🟡 MediumEnsure selfie is live, not a photo
Email notifications🟡 MediumNotify business on verification completion
Retry webhook endpoint🟡 MediumAPI to retry failed webhooks

15.3 Nice-to-Have

FeaturePriorityDescription
Batch verification🟢 LowSubmit multiple verifications at once
Image quality scoring🟢 LowReject blurry/dark images upfront
Document expiry check🟢 LowFlag expired IDs
Multi-language support🟢 LowSDK widgets in local languages
Sandbox mode🟢 LowTest mode with mock responses
GraphQL API🟢 LowAlternative to REST

15.4 SDK Gaps

SDKGap
Node.jsNo retry logic, no streaming upload
ReactNo camera capture (gallery only)
React NativeRequires expo-image-picker (not bare RN)
FlutterNo liveness detection integration
KotlinNo Jetpack Compose UI (Activity only)
SwiftiOS 16+ only, no UIKit support

15.5 Technical Debt

ItemDescription
setImmediate for background jobsShould use proper job queue (Bull/BullMQ)
Single-file uploadsShould support streaming for large files
No retry mechanismFailed processing doesn't retry automatically
No dead letter queueFailed webhooks lost after 3 attempts
No idempotency keysDuplicate submissions not prevented

Appendix A: Environment Variables

env
# Server
PORT=3010
NODE_ENV=production

# Database (Neon PostgreSQL)
DATABASE_URL=postgresql://user:pass@host:5432/yeboverify?sslmode=require

# Cloudflare R2 Storage
R2_ACCOUNT_ID=9f15f12d867a59b212ed2ae3cf4615ca
R2_BUCKET=yeboverify-documents
R2_ACCESS_KEY_ID=
R2_SECRET_ACCESS_KEY=

# AWS Rekognition
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=
AWS_REGION=us-east-1

# Google Gemini OCR
GEMINI_API_KEY=

# Admin API
ADMIN_SECRET=change-this-secret

Appendix B: API Response Codes

HTTP CodeMeaning
200Success
201Created
202Accepted (async processing)
400Bad Request (validation error)
401Unauthorized (missing/invalid auth)
403Forbidden (account disabled)
404Not Found
409Conflict (duplicate)
500Internal Server Error

Appendix C: Glossary

TermDefinition
KYCKnow Your Customer — identity verification requirements
OCROptical Character Recognition — extracting text from images
Face Score0-100 percentage indicating face similarity
WebhookHTTP POST callback when verification completes
HMACHash-based Message Authentication Code
R2Cloudflare's S3-compatible object storage
Presigned URLTemporary, signed URL for private object access

Document generated: March 2026Last updated: March 19, 2026

One chat. Everything done.