This guide explains how to integrate the Flutter Verification SDK into your production application with proper backend setup.
- Architecture Overview
- Backend Setup
- Mobile App Integration
- Webhook Handling
- Security Considerations
- Testing
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
Your backend must implement an endpoint that:
- Authenticates the mobile user
- Calls the verification API with your API key
- Returns the verification_id to the mobile app
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');
});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
// 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,
]);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
}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"
}
}✅ 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
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... ❌
});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' });
}The SDK's AndroidManifest.xml automatically merges with your app's manifest, providing:
-
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" />
-
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 21If 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>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>For development, use verify.test.okid.io which has a default API key configured:
// Example app demonstrates this approach
final data = await BackendService.generateVerificationId();- Get test API key from your verification service dashboard
- Set up test backend with test API key
- Test complete flow:
- Generate verification_id
- Complete verification in app
- Verify webhook received
- Check data in database
- 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
# .env file
OKID_API_KEY=your_production_api_key_here
OKID_BASE_URL=https://verify.test.okid.io
WEBHOOK_SECRET=your_webhook_secret_hereMonitor these metrics:
- Verification generation rate
- Completion rate
- Error rate
- Average completion time
- Webhook delivery success
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
}
}
}For additional help:
- Check the README.md for SDK usage
- Review the example app for working implementation
- Contact support for API key issues