CM04

Identity Verification

Journey: Customer Mobile
Duration: ~30 seconds
Provider: Onfido
Compliance: KYC, AML, PEP Screening
1

User Interface Layer

Mobile-friendly document capture and verification display

📄 Document Type Selector

  • Passport Option: UK/EU passport with machine-readable zone
  • Driving Licence Option: UK photocard driving licence (front and back)
  • Visual Indicators: Icons and labels for each document type
  • Active State: Selected document highlighted with blue border

📸 Upload Options

  • Take Photo: Opens device camera for real-time capture
  • Upload from Gallery: Select existing photo from device storage
  • Drag & Drop: Desktop support for file drag-and-drop
  • File Validation: Only JPG, PNG, PDF accepted (max 10MB)

⏳ Verification Progress Panel

  • Step 1: Document upload (instant)
  • Step 2: OCR extraction (2-3 seconds)
  • Step 3: Document authenticity check (3-4 seconds)
  • Step 4: Biometric face match (2-3 seconds)
  • Step 5: PEP/Sanctions screening (3-5 seconds)
  • Visual Feedback: Animated spinner with progress checkmarks

✅ Verification Result Display

  • Success Card: Green gradient with checkmark icon
  • Extracted Data: Full name, date of birth, document type, expiry date
  • Verification Status: "✓ All Checks Passed" badge
  • Validation Prompt: "Does this information look correct?"
  • Action Buttons: "Yes, Confirm" and "No, Retry" options

💬 AI Assistant Guidance

  • Compliance Message: "I need to verify your identity for regulatory compliance"
  • Checks Listed: Name match, age verification (18+), document validity, PEP/Sanctions
  • Reassurance: "This takes 30 seconds and is FCA-compliant"

🎨 UI Events Captured

  • onSelectDocType(type) → Switches between passport and driving licence
  • onTakePhoto() → Opens camera API for document capture
  • onUploadFile(file) → Validates and uploads document image
  • onConfirmIdentity() → Accepts verification results and proceeds to CM05
  • onRetryVerification() → Allows customer to upload different document
2

API / Backend for Frontend (BFF)

Document upload and verification orchestration

🔌 Document Upload Endpoint

  • Method: POST (multipart/form-data)
  • URL: /api/v1/applications/{application_id}/identity/upload
  • Purpose: Upload identity document and trigger Onfido verification
Request (Multipart Form)
POST /api/v1/applications/app_20251222_143023_usr123/identity/upload
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: multipart/form-data

// Form Fields:
document_type: "passport"  // or "driving_licence"
document_side: "front"     // or "back" for driving licence
file: // Binary image data (JPG/PNG/PDF, max 10MB)
capture_method: "camera"  // or "upload"
Response (200 OK)
{
  "request_id": "req_cm04_20251222_143512",
  "status": "success",
  "verification_id": "onfido_check_abc123xyz",
  "verification_result": {
    "status": "complete",
    "result": "clear",
    "extracted_data": {
      "full_name": "John Smith",
      "date_of_birth": "1985-06-15",
      "document_type": "UK Passport",
      "document_number": "123456789",
      "expiry_date": "2030-06-15",
      "nationality": "British"
    },
    "checks_performed": {
      "document_authenticity": {
        "status": "clear",
        "confidence": 0.99
      },
      "facial_similarity": {
        "status": "clear",
        "confidence": 0.98
      },
      "aml_screening": {
        "status": "clear",
        "pep_match": false,
        "sanctions_match": false,
        "watchlist_match": false
      },
      "director_match": {
        "status": "clear",
        "matched_company": "Smith's Artisan Café Ltd",
        "company_number": "12345678"
      }
    }
  },
  "processing_time_ms": 12340,
  "timestamp": "2025-12-22T14:35:24Z"
}

🔄 Verification Status Endpoint

  • Method: GET
  • URL: /api/v1/identity/verification/{verification_id}/status
  • Purpose: Poll for verification progress (async processing)
  • Polling: Every 2 seconds until status = "complete"
Status Response
{
  "verification_id": "onfido_check_abc123xyz",
  "status": "in_progress",  // or "complete", "failed"
  "current_step": "facial_similarity",
  "progress_percentage": 60,
  "estimated_completion_seconds": 8
}

🔒 Security & Validation

  • File Size Limit: Max 10MB per upload
  • File Type Validation: Only JPEG, PNG, PDF accepted
  • Image Quality Check: Minimum resolution 1280x720
  • Rate Limiting: Max 5 upload attempts per application
  • PII Encryption: All extracted data encrypted before storage

🔍 5-Step KYC Verification Process

1

Document Upload & Pre-Processing

Customer uploads passport or driving licence photo. System validates file format, size, and image quality.

Actions: File validation, image optimization, metadata extraction
Duration: Instant (client-side)
Rejection Criteria: Poor quality, wrong format, file too large
2

OCR Data Extraction

Onfido's AI extracts text data from document (name, DOB, document number, expiry date).

Technology: Machine learning OCR models trained on 2,500+ document types
Duration: 2-3 seconds
Accuracy: 99.2% character recognition rate
Extracted Fields: Name, DOB, document number, issue/expiry dates, nationality
3

Document Authenticity Verification

AI checks for document tampering, forgery, and validates security features.

Checks: Security features (holograms, UV markings), font consistency, layout validation
Duration: 3-4 seconds
AI Model: Trained on 100,000+ genuine and fraudulent documents
Output: Authenticity score 0-100% (requires >85% to pass)
4

Biometric Facial Comparison

Compares photo on document to selfie (if required) using facial recognition.

Technology: Deep learning facial recognition (99.8% accuracy)
Duration: 2-3 seconds
Match Threshold: >95% similarity required
Liveness Detection: Prevents spoofing with photos/videos (optional)
5

AML, PEP & Sanctions Screening

Screens name and DOB against global watchlists for politically exposed persons and sanctions.

Databases: OFAC, UN, EU sanctions, PEP lists from 240+ countries
Duration: 3-5 seconds
Match Logic: Fuzzy name matching with DOB confirmation
Director Verification: Cross-checks with Companies House director records
Result: Clear, Consider, or Reject

Total Processing Time: 10-15 seconds (all checks run in parallel where possible)

3

Business Logic & AI Orchestration

Onfido integration and verification workflow

🤖 Onfido Verification Orchestrator

  • Applicant Creation: Create Onfido applicant profile with customer data
  • Document Upload: Upload document image to Onfido SDK
  • Check Creation: Initiate document + facial_similarity + watchlist_aml checks
  • Webhook Handling: Receive real-time updates on check progress
  • Result Aggregation: Combine all check results into single verdict
Onfido Integration Logic
// Verification Orchestration Flow

async function verifyIdentity(applicationId, documentFile) {
  
  // 1. Create Onfido Applicant
  const applicant = await onfido.createApplicant({
    first_name: customer.firstName,
    last_name: customer.lastName,
    email: customer.email
  });
  
  // 2. Upload Document
  const document = await onfido.uploadDocument({
    applicant_id: applicant.id,
    type: 'passport',
    file: documentFile,
    side: 'front'
  });
  
  // 3. Create Check (runs all verifications)
  const check = await onfido.createCheck({
    applicant_id: applicant.id,
    report_names: [
      'document',           // Authenticity + OCR
      'facial_similarity_photo', // Face match
      'watchlist_aml'       // PEP/Sanctions
    ]
  });
  
  // 4. Poll for Results (or use webhook)
  const result = await pollCheckStatus(check.id);
  
  // 5. Parse and Return
  return {
    status: result.status,  // 'complete'
    result: result.result,  // 'clear', 'consider', 'reject'
    extractedData: parseOnfidoData(result),
    checksPerformed: aggregateChecks(result.reports)
  };
}

📊 Verification Decision Logic

  • Clear: All checks passed → Proceed to credit assessment (CM05)
  • Consider: Minor issues detected → Manual review by compliance team
  • Reject: Failed authenticity or AML hit → Application declined
  • Retry Logic: Up to 3 upload attempts if quality issues detected
Decision Engine
function determineVerificationOutcome(onfidoResult) {
  
  // Extract individual check results
  const documentCheck = onfidoResult.reports.find(r => r.name === 'document');
  const facialCheck = onfidoResult.reports.find(r => r.name === 'facial_similarity_photo');
  const amlCheck = onfidoResult.reports.find(r => r.name === 'watchlist_aml');
  
  // Auto-Reject Criteria
  if (documentCheck.result === 'rejected') {
    return {
      decision: 'REJECT',
      reason: 'Document authenticity failed',
      requiresManualReview: false
    };
  }
  
  if (amlCheck.properties.matches.length > 0) {
    return {
      decision: 'REJECT',
      reason: 'PEP or sanctions match found',
      requiresManualReview: true,  // Compliance review
      matches: amlCheck.properties.matches
    };
  }
  
  // Consider (Manual Review)
  if (documentCheck.result === 'consider' || 
      facialCheck.properties.score < 0.95) {
    return {
      decision: 'CONSIDER',
      reason: 'Document quality or facial match below threshold',
      requiresManualReview: true
    };
  }
  
  // Auto-Approve
  return {
    decision: 'CLEAR',
    reason: 'All checks passed',
    requiresManualReview: false
  };
}

🔄 Director Matching Logic

  • Companies House Lookup: Retrieve list of directors for company
  • Name Matching: Fuzzy match extracted name with director names
  • DOB Verification: Compare DOB if available in Companies House data
  • Result: Pass if name matches and person is active director

⚠️ Fraud Detection Signals

  • Document Tampering: Inconsistent fonts, altered text, cloned sections
  • Photo Substitution: Face photo doesn't match document security features
  • Expired Document: Document past expiry date
  • Underage Applicant: DOB indicates age <18 years
  • Multiple Attempts: Same document uploaded to different applications

✅ Comprehensive Verification Checks

🔍

Document Authenticity

AI analyzes security features: holograms, UV markings, microprinting, font consistency, MRZ validation. Detects tampering, forgery, and photo substitution with 99% accuracy.

📝

OCR Data Extraction

Optical character recognition extracts: full name, date of birth, document number, nationality, issue/expiry dates. Supports 2,500+ document types from 195 countries.

👤

Facial Similarity

Biometric comparison of document photo to selfie using deep learning facial recognition. 99.8% accuracy with liveness detection to prevent spoofing attacks.

🚫

PEP Screening

Checks if applicant is a Politically Exposed Person. Searches PEP databases from 240+ countries including heads of state, senior government officials, and their close associates.

⚖️

Sanctions Lists

Screens against global sanctions lists: OFAC (US), UN, EU, UK HM Treasury. Includes individuals and entities subject to financial restrictions.

👔

Director Verification

Validates that applicant is listed as active director of the company in Companies House records. Matches name and date of birth with official government data.

🎂

Age Verification

Confirms applicant is 18+ years old based on extracted date of birth. Required for legal lending compliance in the UK.

📅

Document Validity

Verifies document is current and not expired. Checks issue and expiry dates against current date. Rejects expired documents automatically.

4

Integration & Middleware Layer

Onfido SDK integration and webhook processing

📡 Onfido Webhook Handler

  • Endpoint: POST /webhooks/onfido
  • Events: check.completed, check.reopened, report.completed
  • Signature Validation: Verify webhook signature using Onfido secret
  • Processing: Update application status based on verification result
Webhook Handler
POST /webhooks/onfido
X-SHA2-Signature: {signature}

{
  "payload": {
    "resource_type": "check",
    "action": "check.completed",
    "object": {
      "id": "onfido_check_abc123xyz",
      "status": "complete",
      "result": "clear",
      "href": "/v3/checks/onfido_check_abc123xyz"
    }
  }
}

// Handler logic:
// 1. Validate webhook signature
// 2. Fetch full check details from Onfido API
// 3. Update application status in database
// 4. Send real-time update to customer UI (WebSocket)

🔄 Real-Time Updates

  • WebSocket Connection: Maintain persistent connection to push updates
  • Progress Events: Send progress updates as each check completes
  • Fallback Polling: If WebSocket unavailable, poll every 2 seconds
  • Timeout Handling: If no response after 60 seconds, show retry option

💾 Document Storage

  • Temporary Storage: S3 bucket for uploaded documents (30-day TTL)
  • Encryption: AES-256 encryption at rest, TLS 1.3 in transit
  • Access Control: Pre-signed URLs with 1-hour expiry for Onfido access
  • Compliance: GDPR-compliant deletion after 30 days unless retained for audit

⚡ Performance Optimization

  • Image Compression: Client-side compression before upload (reduces size by 60%)
  • Parallel Checks: All Onfido reports run simultaneously
  • CDN for SDK: Onfido SDK loaded from CloudFlare CDN
  • Async Processing: Verification runs async, customer receives instant confirmation
5

External Systems Integration

Onfido KYC platform

🛡️
Onfido Identity Verification

AI-powered KYC and AML compliance platform

  • Base URL: https://api.onfido.com/v3
  • Authentication: API Token in Authorization header
  • SDK: Onfido Web SDK for document capture and upload
  • Supported Documents: Passport, driving licence, national ID (2,500+ types from 195 countries)
  • Processing Time: 10-15 seconds average for full KYC check
  • Accuracy: 99% document authenticity detection, 99.8% facial recognition
  • Compliance: FCA-regulated, GDPR-compliant, ISO 27001 certified
  • Cost: ~£2-3 per verification (volume pricing available)
  • Onfido API Endpoints
    // 1. Create Applicant
    POST /v3/applicants
    {
      "first_name": "John",
      "last_name": "Smith",
      "email": "john@example.com"
    }
    
    // 2. Upload Document
    POST /v3/documents
    {
      "applicant_id": "{applicant_id}",
      "type": "passport",
      "side": "front",
      "file": // multipart/form-data
    }
    
    // 3. Create Check
    POST /v3/checks
    {
      "applicant_id": "{applicant_id}",
      "report_names": [
        "document",
        "facial_similarity_photo",
        "watchlist_aml"
      ]
    }
    
    // 4. Retrieve Check Results
    GET /v3/checks/{check_id}
    // Returns: status, result, reports array with detailed findings

    🔗 Secondary Integrations

    • Companies House API: Verify director name matches company records
    • AWS S3: Temporary storage for uploaded document images
    • WebSocket Service: Real-time progress updates to customer UI
    • Notification Service: Email/SMS confirmations after verification complete

    ⚠️ Fallback & Redundancy

    • Onfido Downtime: Queue verification requests for processing when service returns
    • Manual Review: If automated checks inconclusive, route to compliance team
    • Alternative Providers: Jumio, Trulioo as backup KYC providers (not currently integrated)
    6

    Data Persistence Layer

    Identity verification records and compliance audit trail

    💾 Identity Verifications Table

    • Database: PostgreSQL
    • Table: identity_verifications
    • Purpose: Store verification results and extracted identity data
    • Encryption: PII fields encrypted with AES-256
    SQL Schema
    CREATE TABLE identity_verifications (
      verification_id     VARCHAR(50) PRIMARY KEY,
      application_id      VARCHAR(50) REFERENCES applications,
      onfido_check_id     VARCHAR(50),
      onfido_applicant_id VARCHAR(50),
      
      -- Extracted Identity Data (Encrypted)
      full_name           TEXT,
      date_of_birth       DATE,
      document_type       VARCHAR(50),
      document_number     TEXT,
      expiry_date         DATE,
      nationality         VARCHAR(50),
      
      -- Verification Results
      status              VARCHAR(20),  -- 'clear', 'consider', 'reject'
      authenticity_score  DECIMAL(3,2),
      facial_match_score  DECIMAL(3,2),
      aml_status          VARCHAR(20),
      pep_match           BOOLEAN,
      sanctions_match     BOOLEAN,
      director_verified   BOOLEAN,
      
      -- Metadata
      document_image_url  TEXT,  -- S3 pre-signed URL
      processing_time_ms  INTEGER,
      created_at          TIMESTAMP,
      updated_at          TIMESTAMP
    );
    
    CREATE INDEX idx_app_id ON identity_verifications(application_id);
    CREATE INDEX idx_status ON identity_verifications(status);

    📋 Update Applications Table

    • Status Update: "data_reviewed" → "identity_verified"
    • Add Fields: verified_name, verified_dob, kyc_passed
    SQL Update
    UPDATE applications
    SET 
      status = 'identity_verified',
      verified_name = 'John Smith',
      verified_dob = '1985-06-15',
      kyc_passed = true,
      kyc_completed_at = '2025-12-22 14:35:24',
      updated_at = '2025-12-22 14:35:24'
    WHERE application_id = 'app_20251222_143023_usr123';

    📝 Compliance Audit Logs

    • Database: Elasticsearch
    • Index: kyc-audit-logs
    • Retention: 7 years (regulatory requirement)
    • Events Logged: Upload, verification start, each check result, final decision
    Audit Log Entries
    // Log 1: Document uploaded
    {
      "log_id": "log_cm04_20251222_143512",
      "event_type": "identity_document_uploaded",
      "screen": "CM04",
      "user_id": "usr_olivia_thompson",
      "application_id": "app_20251222_143023_usr123",
      "timestamp": "2025-12-22T14:35:12Z",
      "details": {
        "document_type": "passport",
        "file_size_bytes": 2458000,
        "capture_method": "camera",
        "device": "iPhone 14 Pro"
      }
    }
    
    // Log 2: Verification completed
    {
      "log_id": "log_cm04_20251222_143524",
      "event_type": "identity_verification_completed",
      "screen": "CM04",
      "user_id": "usr_olivia_thompson",
      "timestamp": "2025-12-22T14:35:24Z",
      "details": {
        "onfido_check_id": "onfido_check_abc123xyz",
        "status": "clear",
        "checks_passed": {
          "document_authenticity": true,
          "facial_similarity": true,
          "aml_screening": true,
          "director_match": true
        },
        "extracted_name": "John Smith",
        "extracted_dob": "1985-06-15",
        "processing_time_ms": 12340
      }
    }

    🗄️ Data Retention Policy

    • Identity Data: Retained for 7 years (AML compliance)
    • Document Images: Deleted from S3 after 30 days (GDPR right to erasure)
    • Audit Logs: 7-year retention in Elasticsearch
    • Onfido Records: Purged after application finalized (per Onfido data policy)

    ⚠️ Error Handling & Edge Cases

    Document Quality Issues

    Photo is blurry, dark, or document edges cut off

    Response: Quality check fails before Onfido submission
    UI: "Photo quality too low. Please retake in good lighting."
    Action: Allow up to 3 retry attempts

    Expired Document

    Passport or driving licence past expiry date

    Response: 422 Unprocessable Entity
    UI: "Document expired on [date]. Please upload current ID."
    Action: Request alternative valid document

    Document Authenticity Failed

    Onfido detects tampering, forgery, or photo substitution

    Response: Verification status = "rejected"
    UI: "Unable to verify document. Please contact support."
    Action: Flag for manual compliance review

    Facial Match Below Threshold

    Face on document doesn't match selfie (<95% confidence)

    Response: Verification status = "consider"
    UI: "Additional verification required"
    Action: Request new selfie or route to manual review

    PEP or Sanctions Match

    Name appears on PEP or sanctions watchlist

    Response: Verification status = "rejected"
    UI: "Unable to proceed with application"
    Action: Automatic application decline, notify compliance team

    Name Doesn't Match Director

    Extracted name not found in Companies House director list

    Response: Verification status = "consider"
    UI: "Name mismatch detected. Please verify details."
    Action: Manual review by underwriter

    Onfido API Timeout

    Onfido service takes >60 seconds or is unavailable

    Response: 504 Gateway Timeout
    UI: "Verification taking longer than expected. Please wait..."
    Action: Queue request for retry, notify customer when complete

    Underage Applicant

    Date of birth indicates age <18 years

    Response: 422 Unprocessable Entity
    UI: "Must be 18+ to apply for business loan"
    Action: Application automatically declined

    Duplicate Document Upload

    Same document already used for different application

    Response: Fraud alert triggered
    UI: "Document already in use"
    Action: Flag both applications for fraud investigation

    Max Attempts Exceeded

    Customer tried uploading document >5 times

    Response: 429 Too Many Requests
    UI: "Maximum attempts reached. Contact support for assistance."
    Action: Lock verification for 24 hours

    📋 Data Model: Identity Verifications Table

    Field Name Data Type Description Example Value
    verification_id VARCHAR(50) Primary key, unique identifier ver_20251222_143524_usr123
    application_id VARCHAR(50) Foreign key to applications app_20251222_143023_usr123
    onfido_check_id VARCHAR(50) Onfido verification ID onfido_check_abc123xyz
    full_name TEXT Extracted name (encrypted) John Smith
    date_of_birth DATE Extracted DOB 1985-06-15
    document_type VARCHAR(50) Type of ID document UK Passport
    document_number TEXT Document number (encrypted) 123456789
    expiry_date DATE Document expiry date 2030-06-15
    status VARCHAR(20) Verification outcome clear / consider / reject
    authenticity_score DECIMAL(3,2) Document authenticity (0-1) 0.99
    facial_match_score DECIMAL(3,2) Biometric match score (0-1) 0.98
    pep_match BOOLEAN PEP screening result false
    sanctions_match BOOLEAN Sanctions screening result false
    director_verified BOOLEAN Companies House director match true
    processing_time_ms INTEGER Verification duration 12340
    created_at TIMESTAMP Record creation time 2025-12-22 14:35:24

    📊 Verification Status Values

    • clear: All checks passed, proceed to credit assessment (CM05)
    • consider: Manual review required due to low confidence scores or minor issues
    • reject: Failed authenticity check or AML match, application declined
    • pending: Verification in progress (intermediate state)
    • expired: Verification >90 days old, needs re-verification

    ⚖️ Regulatory Compliance & Standards

    🇬🇧 UK KYC Requirements

    • Identity Verification: Must verify identity using government-issued photo ID
    • Age Verification: Applicants must be 18+ for business lending
    • Director Verification: Confirm applicant is listed company director
    • Record Retention: Keep verification records for 5-7 years

    💰 AML Regulations

    • PEP Screening: Check for politically exposed persons
    • Sanctions Lists: Screen against OFAC, UN, EU sanctions
    • Watchlist Monitoring: Ongoing monitoring for sanctions changes
    • Enhanced Due Diligence: Extra checks for high-risk customers

    🔒 GDPR Compliance

    • Data Minimization: Only collect necessary identity data
    • Right to Erasure: Delete documents after 30 days
    • Encryption: All PII encrypted at rest and in transit
    • Consent: Explicit consent for identity verification

    🏛️ FCA Standards

    • Identity Assurance: Use FCA-approved verification methods
    • Fraud Prevention: Implement robust anti-fraud controls
    • Audit Trail: Maintain complete verification audit logs
    • Third-Party Providers: Onfido is FCA-registered
    ⬅️ Back: CM03 Architecture (Consent & Connection) Next: CM05 Architecture (Credit Assessment) ➡️