← Back to Documentation

Custom Verification Provider Guide

Build custom identity verification flows for Cardless ID credentials

Overview

A verification provider is a module that handles the identity verification process and returns verified identity data. Cardless ID then uses this data to issue a W3C Verifiable Credential on the Algorand blockchain.

Production Deployment & Issuer Registry

Cardless ID uses a smart contract as a registry of allowed issuers. Only credentials issued by addresses in this registry will be recognized as valid by verifiers.

For production deployment: You must complete a security audit before we add your issuer address to the on-chain registry. This ensures the integrity of the Cardless ID ecosystem.

Contact us to request addition to the registry

Key Responsibilities

  • ✓ Verify user identity through your chosen method
  • ✓ Return standardized identity data
  • ✓ Provide verification quality metrics
  • ✓ Handle errors and edge cases

Provider Types

1. Full Verification Provider

A full verification provider implements a complete identity verification flow, similar to the default custom verification flow.

Typical Components:

  • Document capture - Photo of government ID
  • OCR/Data extraction - Extract name, DOB, document number
  • Fraud detection - Check for fake or altered documents
  • Biometric verification - Selfie capture and face matching
  • Liveness detection - Ensure real person, not a photo

Use Cases:

  • Cloud-based verification (e.g., Stripe Identity, Persona, Onfido)
  • Custom ML-based verification
  • Hardware-based verification (NFC passport readers)
  • Manual review workflows

2. Delegated Verification Provider

A delegated verification provider issues credentials based on existing verification from a trusted authority. The provider trusts that verification has already occurred and simply signs the credential.

Use Cases:

  • Banks issuing credentials for their customers
  • Government agencies (DMV, Social Security Administration)
  • Universities issuing student credentials
  • Employers issuing employee credentials
  • Healthcare providers issuing patient credentials

See the Delegated Verification Guide for detailed implementation instructions.

Architecture Overview

Provider Interface

All verification providers must implement the VerificationProvider interface:

interface VerificationProvider {
  name: string;

  // Create a new verification session
  createSession(sessionId: string): Promise<{
    authToken: string;
    providerSessionId: string;
  }>;

  // Process verification results
  processVerification(
    sessionId: string,
    providerData: any
  ): Promise<VerifiedIdentity>;

  // Optional: Handle webhooks from provider
  handleWebhook?(payload: any): Promise<void>;
}

interface VerifiedIdentity {
  firstName: string;
  lastName: string;
  dateOfBirth: string; // YYYY-MM-DD
  documentNumber?: string;
  documentType?: 'drivers_license' | 'passport' | 'government_id';
  issuingCountry?: string;
  issuingState?: string;
  compositeHash: string; // Unique identifier

  // Verification quality metrics
  evidence: {
    fraudDetection?: {
      performed: boolean;
      passed: boolean;
      signals: string[];
    };
    documentAnalysis?: {
      bothSidesAnalyzed: boolean;
      lowConfidenceFields: string[];
      qualityLevel: 'high' | 'medium' | 'low';
    };
    biometricVerification?: {
      performed: boolean;
      faceMatch: boolean;
      faceMatchConfidence?: number;
      liveness: boolean;
      livenessConfidence?: number;
    };
  };
}

Directory Structure

app/
├── utils/
│   └── verification-providers/
│       ├── index.ts                    # Provider registry
│       ├── base-provider.ts            # Base class (optional)
│       ├── mock-provider.ts            # Development/testing
│       ├── custom-provider.ts          # Default custom verification
│       ├── stripe-identity-provider.ts # Example: Stripe Identity
│       └── delegated-provider.ts       # Example: Delegated auth
└── routes/
    └── api/
        ├── verification/
        │   ├── start.ts                # Create session
        │   ├── webhook.ts              # Handle provider webhooks
        │   └── status.$id.ts           # Check verification status
        └── custom-verification/
            ├── upload-id.ts            # Custom flow: ID upload
            └── upload-selfie.ts        # Custom flow: Selfie

Building a Full Verification Provider

Step 1: Create Provider File

Create a new file in app/utils/verification-providers/your-provider.ts:

import type { VerificationProvider, VerifiedIdentity } from '~/types/verification';
import { generateCompositeHash } from '~/utils/composite-hash.server';

export class YourVerificationProvider implements VerificationProvider {
  name = 'your-provider';
  private apiKey: string;

  constructor() {
    this.apiKey = process.env.YOUR_PROVIDER_API_KEY || '';
    if (!this.apiKey) {
      console.warn('[YourProvider] API key not configured');
    }
  }

  async createSession(sessionId: string): Promise<{
    authToken: string;
    providerSessionId: string;
  }> {
    // Call your provider's API to create a verification session
    const response = await fetch('https://api.yourprovider.com/verifications', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${this.apiKey}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        callback_url: `${process.env.BASE_URL}/api/verification/webhook`,
        metadata: { cardless_session_id: sessionId }
      })
    });

    const data = await response.json();

    return {
      authToken: data.client_secret,
      providerSessionId: data.id
    };
  }

  async processVerification(
    sessionId: string,
    providerData: any
  ): Promise<VerifiedIdentity> {
    // Fetch verification results from your provider
    const response = await fetch(
      `https://api.yourprovider.com/verifications/${providerData.verification_id}`,
      {
        headers: { 'Authorization': `Bearer ${this.apiKey}` }
      }
    );

    const verification = await response.json();

    // Map provider data to Cardless ID format
    const identity: VerifiedIdentity = {
      firstName: verification.user.first_name,
      lastName: verification.user.last_name,
      dateOfBirth: verification.user.date_of_birth,
      documentNumber: verification.document.number,
      documentType: this.mapDocumentType(verification.document.type),
      issuingCountry: verification.document.country,
      compositeHash: generateCompositeHash(
        verification.user.first_name,
        verification.user.last_name,
        verification.user.date_of_birth
      ),
      evidence: {
        fraudDetection: {
          performed: true,
          passed: verification.fraud_check.passed,
          signals: verification.fraud_check.signals || []
        },
        documentAnalysis: {
          bothSidesAnalyzed: verification.document.sides_captured === 2,
          lowConfidenceFields: [],
          qualityLevel: verification.document.quality
        },
        biometricVerification: {
          performed: true,
          faceMatch: verification.biometric.match,
          faceMatchConfidence: verification.biometric.confidence,
          liveness: verification.biometric.liveness_passed,
          livenessConfidence: verification.biometric.liveness_confidence
        }
      }
    };

    return identity;
  }

  private mapDocumentType(providerType: string): 'drivers_license' | 'passport' | 'government_id' {
    const mapping: Record<string, any> = {
      'driving_license': 'drivers_license',
      'passport': 'passport',
      'id_card': 'government_id'
    };
    return mapping[providerType] || 'government_id';
  }
}

Step 2: Register Provider

Add your provider to app/utils/verification-providers/index.ts:

import { YourVerificationProvider } from './your-provider';

const providers = {
  mock: new MockProvider(),
  custom: new CustomProvider(),
  'your-provider': new YourVerificationProvider(),
};

export function getProvider(name?: string): VerificationProvider {
  const providerName = name || 'mock';
  const provider = providers[providerName];

  if (!provider) {
    console.warn(`Provider "${providerName}" not found, using mock`);
    return providers.mock;
  }

  return provider;
}

Step 3: Configure Environment

YOUR_PROVIDER_API_KEY=sk_live_xxxxxxxxxxxxx

Step 4: Use Provider

// Start verification with your provider
const response = await fetch('/api/verification/start', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ provider: 'your-provider' })
});

const { authToken, sessionId } = await response.json();

// Use authToken with your provider's SDK

API Reference

POST /api/verification/start

Create a new verification session.

Request:

{
  "provider": "your-provider" // optional, defaults to "mock"
}

Response:

{
  "sessionId": "session_1234567890_abc123",
  "authToken": "client_secret_xxx",
  "expiresAt": "2025-01-15T10:30:00Z",
  "provider": "your-provider"
}

GET /api/verification/status/:sessionId

Check verification session status.

Response:

{
  "sessionId": "session_1234567890_abc123",
  "status": "pending" | "completed" | "failed",
  "provider": "your-provider",
  "verifiedData": { /* VerifiedIdentity if completed */ }
}

Security Considerations

🚨 Mock Provider Production Protection

The mock verification provider is automatically blocked in production environments.

  • • Mock provider throws an error if NODE_ENV=production
  • • In production, the default provider is cardlessid (Google Document AI + AWS)
  • • Set VERIFICATION_PROVIDER environment variable to override the default
  • • Valid production values: cardlessid, custom
  • • To temporarily allow mock in production for testing (NOT RECOMMENDED), set ALLOW_MOCK_VERIFICATION=true

API Key Management

  • • Store API keys in environment variables
  • • Rotate keys regularly
  • • Use separate keys for dev/staging/production

Webhook Verification

  • • Verify webhook signatures from providers
  • • Use HTTPS for all webhook endpoints
  • • Implement replay protection

Data Retention

  • • Delete ID photos after verification
  • • Store only minimal PII
  • • Comply with GDPR/CCPA requirements

Fraud Prevention

  • • Implement rate limiting
  • • Monitor for duplicate composite hashes
  • • Log all verification attempts

Related Documentation

Support

  • 📧 Email: me@djscruggs.com
  • 🐛 Issues: GitHub Issues
  • 💬 Community: Discord (coming soon)