Skip to content

Latest commit

 

History

History
635 lines (508 loc) · 16.6 KB

File metadata and controls

635 lines (508 loc) · 16.6 KB

Integration Guide

This guide explains how to integrate the Flutter Verification SDK into your production application with proper backend setup.

Table of Contents


Architecture Overview

The verification system follows a secure client-server architecture:

┌─────────────────┐
│   Flutter App   │
└────────┬────────┘
         │
         │ 1. Request verification
         ▼
┌─────────────────────┐
│   Your Backend      │ ◄── API Key stored here (secure)
│   (Node/Python/PHP) │
└─────────┬───────────┘
          │
          │ 2. Generate verification_id
          ▼
┌──────────────────────────┐
│  verify.test.okid.io     │
│  (Verification Service)  │
└──────────┬───────────────┘
           │
           │ 3. Return verification_id
           ▼
┌─────────────────┐
│  Flutter App    │
│  (Opens WebView)│
└─────────────────┘
           │
           │ 4. User completes verification
           ▼
┌──────────────────────────┐
│  verify.test.okid.io     │
│  (Process verification)  │
└──────────┬───────────────┘
           │
           │ 5. Webhook notification
           ▼
┌─────────────────────┐
│   Your Backend      │
│   (Store results)   │
└─────────────────────┘

Key Points:

  • API key NEVER leaves your backend server
  • verification_id is a short-lived session token
  • Mobile app only receives and uses verification_id
  • Results sent via webhook to your backend

Backend Setup

Your backend must implement an endpoint that:

  1. Authenticates the mobile user
  2. Calls the verification API with your API key
  3. Returns the verification_id to the mobile app

Node.js/Express

const express = require('express');
const fetch = require('node-fetch');

const app = express();
app.use(express.json());

// Middleware to authenticate your users
const authenticateUser = (req, res, next) => {
  const token = req.headers.authorization?.split(' ')[1];
  // Verify your user's JWT or session token
  // ...
  req.user = { id: 'user_123' }; // Your user data
  next();
};

// Endpoint for mobile app to request verification
app.post('/api/mobile/generate-verification', authenticateUser, async (req, res) => {
  try {
    const userId = req.user.id;
    
    // Call verification service
    const response = await fetch('https://verify.test.okid.io/api/generate-verification', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-SDK-Key': process.env.OKID_API_KEY,  // Your API key from environment variable
      },
      body: JSON.stringify({
        extra_data: {
          user_id: userId,
          timestamp: new Date().toISOString(),
          // Add any custom data you want associated with this verification
        }
      }),
    });
    
    if (!response.ok) {
      throw new Error(`Verification API error: ${response.status}`);
    }
    
    const data = await response.json();
    
    // Optional: Store verification in your database
    await db.verifications.create({
      verification_id: data.verificationId,
      user_id: userId,
      status: 'pending',
      created_at: new Date(),
      expires_at: data.expiresAt,
    });
    
    // Return verification_id to mobile app
    res.json({
      verificationId: data.verificationId,
      expiresAt: data.expiresAt,
    });
    
  } catch (error) {
    console.error('Error generating verification:', error);
    res.status(500).json({ error: 'Failed to generate verification' });
  }
});

// Webhook endpoint to receive results
app.post('/api/webhooks/verification-complete', async (req, res) => {
  try {
    const { verificationId, status, results } = req.body;
    
    // Update verification in your database
    await db.verifications.update({
      status: status,
      results: results,
      completed_at: new Date(),
    }, {
      where: { verification_id: verificationId }
    });
    
    // Notify user via push notification, email, etc.
    await notifyUser(verificationId, status);
    
    res.json({ message: 'Webhook processed' });
    
  } catch (error) {
    console.error('Webhook error:', error);
    res.status(500).json({ error: 'Webhook processing failed' });
  }
});

app.listen(3000, () => {
  console.log('Backend server running on port 3000');
});

Python/Flask

from flask import Flask, request, jsonify
import requests
import os
from datetime import datetime

app = Flask(__name__)

# Middleware to authenticate users
def authenticate_user():
    token = request.headers.get('Authorization', '').split(' ')[-1]
    # Verify your user's JWT or session token
    # ...
    return {'id': 'user_123'}  # Your user data

@app.route('/api/mobile/generate-verification', methods=['POST'])
def generate_verification():
    try:
        # Authenticate user
        user = authenticate_user()
        if not user:
            return jsonify({'error': 'Unauthorized'}), 401
        
        user_id = user['id']
        
        # Call verification service
        response = requests.post(
            'https://verify.test.okid.io/api/generate-verification',
            headers={
                'Content-Type': 'application/json',
                'X-SDK-Key': os.environ.get('OKID_API_KEY'),
            },
            json={
                'extra_data': {
                    'user_id': user_id,
                    'timestamp': datetime.now().isoformat(),
                }
            }
        )
        
        response.raise_for_status()
        data = response.json()
        
        # Optional: Store in database
        # db.verifications.insert({
        #     'verification_id': data['verificationId'],
        #     'user_id': user_id,
        #     'status': 'pending',
        #     'created_at': datetime.now(),
        # })
        
        return jsonify({
            'verificationId': data['verificationId'],
            'expiresAt': data.get('expiresAt'),
        })
        
    except requests.RequestException as e:
        print(f'Error generating verification: {e}')
        return jsonify({'error': 'Failed to generate verification'}), 500

@app.route('/api/webhooks/verification-complete', methods=['POST'])
def verification_webhook():
    try:
        data = request.get_json()
        verification_id = data.get('verificationId')
        status = data.get('status')
        results = data.get('results')
        
        # Update database
        # db.verifications.update(
        #     {'verification_id': verification_id},
        #     {'status': status, 'results': results, 'completed_at': datetime.now()}
        # )
        
        # Notify user
        # notify_user(verification_id, status)
        
        return jsonify({'message': 'Webhook processed'})
        
    except Exception as e:
        print(f'Webhook error: {e}')
        return jsonify({'error': 'Webhook processing failed'}), 500

if __name__ == '__main__':
    app.run(debug=False, port=3000)

PHP

<?php
// generate_verification.php

header('Content-Type: application/json');

// Authenticate user
$user = authenticateUser(); // Your authentication logic
if (!$user) {
    http_response_code(401);
    echo json_encode(['error' => 'Unauthorized']);
    exit;
}

$userId = $user['id'];

// Prepare request
$data = [
    'extra_data' => [
        'user_id' => $userId,
        'timestamp' => date('c'),
    ]
];

$options = [
    'http' => [
        'header' => "Content-Type: application/json\r\n" .
                   "X-SDK-Key: " . getenv('OKID_API_KEY') . "\r\n",
        'method' => 'POST',
        'content' => json_encode($data),
    ]
];

$context = stream_context_create($options);
$response = file_get_contents(
    'https://verify.test.okid.io/api/generate-verification',
    false,
    $context
);

if ($response === false) {
    http_response_code(500);
    echo json_encode(['error' => 'Failed to generate verification']);
    exit;
}

$result = json_decode($response, true);

// Optional: Store in database
// $pdo->prepare("INSERT INTO verifications (verification_id, user_id, status, created_at) VALUES (?, ?, ?, NOW())")
//     ->execute([$result['verificationId'], $userId, 'pending']);

echo json_encode([
    'verificationId' => $result['verificationId'],
    'expiresAt' => $result['expiresAt'] ?? null,
]);

Mobile App Integration

Once your backend is set up, integrate the SDK in your Flutter app:

import 'package:flutter_verification_sdk/flutter_verification_sdk.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';

class VerificationService {
  final String backendUrl;
  final String authToken;
  
  VerificationService({
    required this.backendUrl,
    required this.authToken,
  });
  
  /// Start verification flow
  Future<VerificationResult> startVerification(BuildContext context) async {
    try {
      // Step 1: Get verification_id from YOUR backend
      final response = await http.post(
        Uri.parse('$backendUrl/api/mobile/generate-verification'),
        headers: {
          'Authorization': 'Bearer $authToken',
          'Content-Type': 'application/json',
        },
      );
      
      if (response.statusCode != 200) {
        throw Exception('Failed to generate verification');
      }
      
      final data = jsonDecode(response.body);
      final verificationId = data['verificationId'] as String;
      
      // Step 2: Launch SDK
      final result = await VerificationSDK.startVerification(
        context: context,
        verificationId: verificationId,
        config: VerificationConfig.production(),
      );
      
      return result;
      
    } catch (e) {
      return VerificationResult.error(e.toString());
    }
  }
}

// Usage in your widget
class MyVerificationPage extends StatelessWidget {
  final verificationService = VerificationService(
    backendUrl: 'https://your-api.com',
    authToken: 'user-auth-token',
  );
  
  Future<void> _startVerification(BuildContext context) async {
    final result = await verificationService.startVerification(context);
    
    if (result.isSuccessful) {
      // Update UI, navigate to success screen
      print('Verification complete!');
    } else if (result.hasError) {
      // Show error message
      ScaffoldMessenger.of(context).showSnackBar(
        SnackBar(content: Text('Error: ${result.errorMessage}')),
      );
    }
  }
  
  // ... rest of your widget
}

Webhook Handling

Configure webhooks in your verification service dashboard to receive real-time updates:

Webhook URL: https://your-api.com/api/webhooks/verification-complete

Webhook Payload:

{
  "verificationId": "ver_abc123...",
  "status": "verified",
  "results": {
    "document": {
      "type": "passport",
      "number": "AB123456",
      "verified": true
    },
    "liveness": {
      "score": 0.95,
      "passed": true
    },
    "user_data": {
      "name": "John Doe",
      "date_of_birth": "1990-01-01"
    }
  },
  "extra_data": {
    "user_id": "user_123",
    "timestamp": "2025-10-31T12:00:00Z"
  }
}

Security Considerations

API Key Protection

DO:

  • Store API key in environment variables
  • Use secrets management (AWS Secrets Manager, HashiCorp Vault)
  • Rotate keys regularly
  • Use separate keys for development/production

DON'T:

  • Hardcode API key in source code
  • Commit API key to version control
  • Include API key in mobile app
  • Share API key in logs or error messages

User Authentication

Always authenticate users before generating verification IDs:

// Good: Verify user's JWT before generating verification
app.post('/generate-verification', authenticateJWT, async (req, res) => {
  const userId = req.user.id; // From verified JWT
  // Generate verification...
});

// Bad: No authentication - anyone can generate verifications
app.post('/generate-verification', async (req, res) => {
  // Generate verification... ❌
});

Verification ID Validation

Track and validate verification IDs to prevent abuse:

// Store verification ownership
await db.verifications.create({
  verification_id: data.verificationId,
  user_id: userId,
  status: 'pending',
  created_at: new Date(),
});

// When receiving webhook, verify ownership
const verification = await db.verifications.findOne({
  where: { verification_id: verificationId, user_id: userId }
});

if (!verification) {
  return res.status(403).json({ error: 'Verification not found' });
}

Platform-Specific Configuration

Android Setup

The SDK's AndroidManifest.xml automatically merges with your app's manifest, providing:

  1. Permissions (automatically included):

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-feature android:name="android.hardware.camera" android:required="false" />
  2. FileProvider (automatically included):

    • Required for camera capture in WebView
    • Authority: ${applicationId}.flutter_inappwebview_android.fileprovider
    • Handles temporary photo storage

Required in your app's android/app/build.gradle:

minSdkVersion 21

If you have a custom FileProvider:

Ensure your FileProvider uses a different authority to avoid conflicts:

<!-- Your custom FileProvider -->
<provider
    android:name="androidx.core.content.FileProvider"
    android:authorities="${applicationId}.myapp.fileprovider"  <!-- Different authority -->
    android:exported="false"
    android:grantUriPermissions="true">
    ...
</provider>

iOS Setup

Add to ios/Runner/Info.plist:

<key>NSCameraUsageDescription</key>
<string>Camera access is required for document and liveness verification</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Photo library access to upload document images</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Location verification for identity confirmation</string>

Testing

Development Environment

For development, use verify.test.okid.io which has a default API key configured:

// Example app demonstrates this approach
final data = await BackendService.generateVerificationId();

Production Testing

  1. Get test API key from your verification service dashboard
  2. Set up test backend with test API key
  3. Test complete flow:
    • Generate verification_id
    • Complete verification in app
    • Verify webhook received
    • Check data in database

Test Checklist

  • Backend generates verification_id successfully
  • Mobile app receives verification_id
  • WebView loads verification UI
  • Camera permissions work
  • Document upload works
  • Liveness check works
  • Results return to mobile app
  • Webhook received on backend
  • Data saved to database
  • User notified of result

Production Deployment

Environment Variables

# .env file
OKID_API_KEY=your_production_api_key_here
OKID_BASE_URL=https://verify.test.okid.io
WEBHOOK_SECRET=your_webhook_secret_here

Monitoring

Monitor these metrics:

  • Verification generation rate
  • Completion rate
  • Error rate
  • Average completion time
  • Webhook delivery success

Error Handling

Implement retry logic for API calls:

async function generateVerificationWithRetry(userId, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await generateVerification(userId);
    } catch (error) {
      if (attempt === maxRetries) throw error;
      await sleep(1000 * attempt); // Exponential backoff
    }
  }
}

Support

For additional help:

  • Check the README.md for SDK usage
  • Review the example app for working implementation
  • Contact support for API key issues