v2client Integration Guide
Integrate okID verification into your application using the v2client portal. Complete server-to-server integration in under 15 minutes.
Prerequisites
okID Dashboard Access
Access to v2dashboard to create and manage API keys
Server Environment
Any server that can make HTTP requests (Node.js, Python, PHP, Go, etc.)
v2client Portal URL
The hosted v2client instance URL (e.g., https://verify.test.okid.io)
How v2client Integration Works
1. Get API Key
Create API key in v2dashboard
2. Generate Verification
Server calls v2client with API key
3. Redirect User
User completes verification in portal
Your Server → v2client Portal → okID Backend │ │ │ │ 1. POST │ 2. Create │ │ /generate- │ verification │ │ verification │ using API key │ │ + API key ────┼───────────────▶│ │ │ │ │ 3. Redirect │ 4. User │ │ user to │ verification │ │ portal ──────▶│ flow ─────────▶│
1Get Your API Key
Create an API key in the v2dashboard. This key authenticates your server when generating verification IDs.
Security Requirements
API keys are for server-side use only. Never expose them in client-side code, URLs, or logs.
Creating Your API Key:
- Log into your v2dashboard
- Navigate to the "API Keys" section
- Click "Create API Key"
- Configure verification modules (document, liveness, form_data)
- Set retention period and other settings
- Save the API key securely in your server environment
Store Securely
Store your API key as an environment variable or in a secure configuration system.
# Environment variable (recommended) API_KEY=your-api-key-here V2CLIENT_URL=https://verify.test.okid.io
2Generate Verification ID
When a user needs to verify their identity, your server makes a request to the v2client portal to generate a verification ID.
Verification Modes
Direct Mode: /?verification_id=xyz
- User goes straight to verification
Selection Mode: /?verification_id=xyz&mode=select
- User chooses QR code or same device
Server-Side Only
This step must be performed on your server. API keys are never sent to browsers.
API Endpoint:
POST {v2client_url}/api/generate-verification Headers: Content-Type: application/json X-SDK-Key: your-api-key-here Response: { "verificationId": "ver_abc123...", "expiresAt": "2024-01-01T12:00:00Z", "modules": {...}, "flow": ["terms", "document", "liveness"] }
cURL Examples:
Linux / macOS:
curl -X POST "https://verify.test.okid.io/api/generate-verification" \ -H "Content-Type: application/json" \ -H "X-SDK-Key: your-api-key-here" # Example response: # { # "verificationId": "ver_abc123...", # "expiresAt": "2024-01-01T12:00:00Z", # "modules": {...}, # "flow": ["terms", "document", "liveness"] # }
Windows (CMD):
curl -X POST "https://verify.test.okid.io/api/generate-verification" ^ -H "Content-Type: application/json" ^ -H "X-SDK-Key: your-api-key-here"
Windows (PowerShell):
$headers = @{ "Content-Type" = "application/json" "X-SDK-Key" = "your-api-key-here" } Invoke-RestMethod -Uri "https://verify.test.okid.io/api/generate-verification" \ -Method POST -Headers $headers
Quick cURL Example
curl -X POST "https://verify.test.okid.io/api/generate-verification" -H "Content-Type: application/json" -H "X-SDK-Key: your-api-key-here"
Example Implementation:
Node.js / Express:
app.post('/verify-user', async (req, res) => { try { // Generate verification ID using your API key const response = await fetch(`${process.env.V2CLIENT_URL}/api/generate-verification`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-SDK-Key': process.env.API_KEY } }); if (!response.ok) { throw new Error('Failed to generate verification'); } const { verificationId } = await response.json(); // Option 1: Direct verification (skip selection screen) const directUrl = `${process.env.V2CLIENT_URL}/?verification_id=${verificationId}`; // Option 2: Show selection screen (QR code + same device choice) const selectUrl = `${process.env.V2CLIENT_URL}/?verification_id=${verificationId}&mode=select`; // Redirect to your preferred mode res.redirect(directUrl); // or selectUrl for selection screen } catch (error) { res.status(500).json({ error: 'Verification generation failed' }); } });
Python / Flask:
import os import requests from flask import redirect @app.route('/verify-user', methods=['POST']) def verify_user(): try: # Generate verification ID using your API key response = requests.post( f"{os.getenv('V2CLIENT_URL')}/api/generate-verification", headers={ 'Content-Type': 'application/json', 'X-SDK-Key': os.getenv('API_KEY') } ) response.raise_for_status() verification_data = response.json() verification_id = verification_data['verificationId'] # Option 1: Direct verification (skip selection screen) direct_url = f"{os.getenv('V2CLIENT_URL')}/?verification_id={verification_id}" # Option 2: Show selection screen (QR code + same device choice) select_url = f"{os.getenv('V2CLIENT_URL')}/?verification_id={verification_id}&mode=select" # Redirect to your preferred mode return redirect(direct_url) # or select_url for selection screen except Exception as e: return {'error': 'Verification generation failed'}, 500
PHP:
<?php function generateVerification() { $v2clientUrl = $_ENV['V2CLIENT_URL']; $apiKey = $_ENV['API_KEY']; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "$v2clientUrl/api/generate-verification"); curl_setopt($ch, CURLOPT_POST, 1); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Content-Type: application/json', "X-SDK-Key: $apiKey" ]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode === 200) { $data = json_decode($response, true); $verificationId = $data['verificationId']; // Option 1: Direct verification (skip selection screen) $directUrl = "$v2clientUrl/?verification_id=$verificationId"; // Option 2: Show selection screen (QR code + same device choice) $selectUrl = "$v2clientUrl/?verification_id=$verificationId&mode=select"; // Redirect to your preferred mode header("Location: $directUrl"); // or $selectUrl for selection screen exit; } else { http_response_code(500); echo json_encode(['error' => 'Verification generation failed']); } } ?>
3User Verification Flow
After redirecting the user to the v2client portal, they will complete the verification process automatically.
What Happens Next:
- User lands on the v2client verification portal
- Portal validates the verification ID
- User completes verification modules (terms, document, liveness, forms)
- Results are processed and stored
- User sees completion status
- Verification results are available via API or dashboard
Example User Journey:
URL: https://verify.test.okid.io/verify?verification_id=ver_abc123
Step 1: Accept terms of service
Step 2: Upload ID document (front/back)
Step 3: Take selfie for liveness detection
Step 4: Fill any additional form data
Step 5: Verification complete!
4Retrieve Verification Results
After the user completes verification, you can programmatically retrieve the results using your API key.
API Key Authentication
Your API key can only retrieve verifications it created, ensuring data security and access control.
API Endpoint:
GET {v2client_url}/api/get-verification-details/{verification_id} Headers: X-SDK-Key: your-api-key-here Response: { "verification_id": "ver_abc123...", "status": { "current": "verified", "updated_at": "2024-01-01T12:00:00Z", "progress": { "document": "completed", "liveness": "completed", "form_data": "completed" } }, "results": { "document": {...}, "liveness": {...}, "form_data": {...} } }
cURL Examples:
Linux / macOS:
curl -X GET "https://verify.test.okid.io/api/get-verification-details/ver_abc123..." \ -H "X-SDK-Key: your-api-key-here" # Example response for verified status: # { # "verification_id": "ver_abc123...", # "status": { # "current": "verified", # "updated_at": "2024-01-01T12:00:00Z", # "progress": { # "document": "completed", # "liveness": "completed" # } # } # }
Windows (CMD):
curl -X GET "https://verify.test.okid.io/api/get-verification-details/ver_abc123..." ^ -H "X-SDK-Key: your-api-key-here"
Windows (PowerShell):
$headers = @{ "X-SDK-Key" = "your-api-key-here" } Invoke-RestMethod -Uri "https://verify.test.okid.io/api/get-verification-details/ver_abc123..." \ -Method GET -Headers $headers
Quick cURL Example
curl -X GET "https://verify.test.okid.io/api/get-verification-details/ver_abc123..." -H "X-SDK-Key: your-api-key-here"
Implementation Examples:
Node.js / Express:
app.get('/verification-results/:id', async (req, res) => { try { const { id: verificationId } = req.params; const response = await fetch(`${process.env.V2CLIENT_URL}/api/get-verification-details/${verificationId}`, { method: 'GET', headers: { 'X-SDK-Key': process.env.API_KEY } }); if (!response.ok) { if (response.status === 403) { return res.status(403).json({ error: 'API key does not have access to this verification' }); } throw new Error('Failed to get verification status'); } const verificationData = await response.json(); res.json(verificationData); } catch (error) { res.status(500).json({ error: 'Failed to retrieve verification results' }); } });
Python / Flask:
import os import requests @app.route('/verification-results/<verification_id>') def get_verification_results(verification_id): try: response = requests.get( f"{os.getenv('V2CLIENT_URL')}/api/get-verification-details/{verification_id}", headers={ 'X-SDK-Key': os.getenv('API_KEY') } ) if response.status_code == 403: return {'error': 'API key does not have access to this verification'}, 403 response.raise_for_status() return response.json() except Exception as e: return {'error': 'Failed to retrieve verification results'}, 500
PHP:
<?php function getVerificationResults($verificationId) { $v2clientUrl = $_ENV['V2CLIENT_URL']; $apiKey = $_ENV['API_KEY']; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, "$v2clientUrl/api/get-verification-details/$verificationId"); curl_setopt($ch, CURLOPT_HTTPHEADER, [ "X-SDK-Key: $apiKey" ]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode === 200) { return json_decode($response, true); } elseif ($httpCode === 403) { throw new Exception('API key does not have access to this verification'); } else { throw new Exception('Failed to retrieve verification results'); } } ?>
Security Note
API keys can only access verifications they created. Attempting to access another organization's verification will result in a 403 Forbidden error.
Alternative: Polling for Results
Instead of making one-time calls, you can poll the verification status endpoint to check for completion:
// Polling example - check every 5 seconds async function pollVerificationStatus(verificationId) { const maxAttempts = 60; // 5 minutes max let attempts = 0; while (attempts < maxAttempts) { try { const response = await fetch(`${process.env.V2CLIENT_URL}/api/get-verification-details/${verificationId}`, { headers: { 'X-SDK-Key': process.env.API_KEY } }); if (response.ok) { const data = await response.json(); // Check if verification is complete if (['verified', 'rejected', 'expired'].includes(data.status.current)) { console.log('Verification complete:', data.status.current); return data; } } // Wait 5 seconds before next check await new Promise(resolve => setTimeout(resolve, 5000)); attempts++; } catch (error) { console.error('Polling error:', error); break; } } throw new Error('Verification polling timeout'); }
Best Practice
For production applications, consider using webhooks instead of polling for better efficiency and real-time notifications.
Retrieving Document Images
Along with verification results, you can also retrieve the document images uploaded during the verification process. Two endpoints are available depending on your security requirements:
Redacted Images (Secured)
/api/get-verification-image/{verification_id}
Applies redaction based on your API key settings (MRZ, portrait, template fields). X-SDK-Key header is mandatory - no fallback to server keys.
Unredacted Images (Secured)
/api/get-verification-image-unredacted/{verification_id}
Returns original images with watermark only. X-SDK-Key header is mandatory - no fallback to server keys.
Node.js Example - Retrieving Images:
// Get redacted image (requires API key) app.get('/verification-image/:id', async (req, res) => { try { const { id: verificationId } = req.params; const response = await fetch(`${process.env.V2CLIENT_URL}/api/get-verification-image/${verificationId}`, { headers: { 'X-SDK-Key': process.env.API_KEY } }); if (response.ok) { const imageBuffer = await response.arrayBuffer(); const contentType = response.headers.get('content-type') || 'image/jpeg'; res.set('Content-Type', contentType); res.send(Buffer.from(imageBuffer)); } else { res.status(response.status).json({ error: 'Failed to retrieve image' }); } } catch (error) { res.status(500).json({ error: 'Image retrieval failed' }); } }); // Get unredacted image (no API key needed) app.get('/verification-image-unredacted/:id', async (req, res) => { try { const { id: verificationId } = req.params; const response = await fetch(`${process.env.V2CLIENT_URL}/api/get-verification-image-unredacted/${verificationId}`); if (response.ok) { const imageBuffer = await response.arrayBuffer(); const contentType = response.headers.get('content-type') || 'image/jpeg'; res.set('Content-Type', contentType); res.send(Buffer.from(imageBuffer)); } else { res.status(response.status).json({ error: 'Failed to retrieve unredacted image' }); } } catch (error) { res.status(500).json({ error: 'Image retrieval failed' }); } });
Python Example - Retrieving Images:
import os import requests from flask import Response @app.route('/verification-image/<verification_id>') def get_verification_image(verification_id): """Get redacted image (requires API key)""" try: response = requests.get( f"{os.getenv('V2CLIENT_URL')}/api/get-verification-image/{verification_id}", headers={'X-SDK-Key': os.getenv('API_KEY')} ) if response.status_code == 200: return Response( response.content, mimetype=response.headers.get('content-type', 'image/jpeg') ) else: return {'error': 'Failed to retrieve image'}, response.status_code except Exception as e: return {'error': 'Image retrieval failed'}, 500 @app.route('/verification-image-unredacted/<verification_id>') def get_verification_image_unredacted(verification_id): """Get unredacted image (no API key needed)""" try: response = requests.get( f"{os.getenv('V2CLIENT_URL')}/api/get-verification-image-unredacted/{verification_id}" ) if response.status_code == 200: return Response( response.content, mimetype=response.headers.get('content-type', 'image/jpeg') ) else: return {'error': 'Failed to retrieve unredacted image'}, response.status_code except Exception as e: return {'error': 'Image retrieval failed'}, 500
⭐ New: Direct Front/Back Image Endpoints
Front Image
/api/get-verification-image-front/{verification_id}
Automatically finds the accepted front document image. No need to determine attempt indices.
Back Image
/api/get-verification-image-back/{verification_id}
Automatically finds the accepted back document image. No need to determine attempt indices.
Node.js Example - Front/Back Images:
// Get front document image (automatically finds accepted attempt) app.get('/verification-image-front/:id', async (req, res) => { try { const { id: verificationId } = req.params; const imageType = req.query.image_type || 'original'; // optional const response = await fetch(`${process.env.V2CLIENT_URL}/api/get-verification-image-front/${verificationId}?image_type=${imageType}`, { headers: { 'X-SDK-Key': process.env.API_KEY } }); if (response.ok) { const imageBuffer = await response.arrayBuffer(); const contentType = response.headers.get('content-type') || 'image/jpeg'; res.set('Content-Type', contentType); res.set('Content-Disposition', 'inline; filename="front_document.jpg"'); res.send(Buffer.from(imageBuffer)); } else { res.status(response.status).json({ error: 'Failed to retrieve front image' }); } } catch (error) { res.status(500).json({ error: 'Front image retrieval failed' }); } }); // Get back document image (automatically finds accepted attempt) app.get('/verification-image-back/:id', async (req, res) => { try { const { id: verificationId } = req.params; const imageType = req.query.image_type || 'original'; // optional const response = await fetch(`${process.env.V2CLIENT_URL}/api/get-verification-image-back/${verificationId}?image_type=${imageType}`, { headers: { 'X-SDK-Key': process.env.API_KEY } }); if (response.ok) { const imageBuffer = await response.arrayBuffer(); const contentType = response.headers.get('content-type') || 'image/jpeg'; res.set('Content-Type', contentType); res.set('Content-Disposition', 'inline; filename="back_document.jpg"'); res.send(Buffer.from(imageBuffer)); } else { res.status(response.status).json({ error: 'Failed to retrieve back image' }); } } catch (error) { res.status(500).json({ error: 'Back image retrieval failed' }); } });
Python Example - Front/Back Images:
@app.route('/verification-image-front/<verification_id>') def get_verification_image_front(verification_id): """Get front document image (automatically finds accepted attempt)""" try: image_type = request.args.get('image_type', 'original') response = requests.get( f"{os.getenv('V2CLIENT_URL')}/api/get-verification-image-front/{verification_id}", headers={'X-SDK-Key': os.getenv('API_KEY')}, params={'image_type': image_type} ) if response.status_code == 200: return Response( response.content, mimetype=response.headers.get('content-type', 'image/jpeg'), headers={'Content-Disposition': 'inline; filename="front_document.jpg"'} ) else: return {'error': 'Failed to retrieve front image'}, response.status_code except Exception as e: return {'error': 'Front image retrieval failed'}, 500 @app.route('/verification-image-back/<verification_id>') def get_verification_image_back(verification_id): """Get back document image (automatically finds accepted attempt)""" try: image_type = request.args.get('image_type', 'original') response = requests.get( f"{os.getenv('V2CLIENT_URL')}/api/get-verification-image-back/{verification_id}", headers={'X-SDK-Key': os.getenv('API_KEY')}, params={'image_type': image_type} ) if response.status_code == 200: return Response( response.content, mimetype=response.headers.get('content-type', 'image/jpeg'), headers={'Content-Disposition': 'inline; filename="back_document.jpg"'} ) else: return {'error': 'Failed to retrieve back image'}, response.status_code except Exception as e: return {'error': 'Back image retrieval failed'}, 500
✅ Advantages of Front/Back Endpoints
- No attempt index needed: Automatically finds the accepted image for the requested side
- Side-specific: Explicitly get front or back without guessing
- Cleaner workflows: Perfect for operators who need final document images
- Error handling: Returns 404 if no accepted image found for that side
Image Security
Redacted images: Use for displaying to operators - sensitive data is blacked out based on your API key settings.
Unredacted images: Use with caution - contains full document data. Only share verification IDs with authorized parties.
Optional Parameters
?attempt_index=0
- Specify which document upload attempt (default: 0)
?image_type=original
- Specify image type (original, cropped, etc.)
cURL Examples for Image Retrieval:
Redacted Images (requires API key):
Linux / macOS:
# Get redacted image (applies API key redaction settings) curl -X GET "https://verify.test.okid.io/api/get-verification-image/ver_abc123..." \ -H "X-SDK-Key: your-api-key-here" \ -o "document-redacted.jpg" # With optional parameters curl -X GET "https://verify.test.okid.io/api/get-verification-image/ver_abc123...?attempt_index=0&image_type=original" \ -H "X-SDK-Key: your-api-key-here" \ -o "document-redacted-original.jpg"
Windows (CMD):
curl -X GET "https://verify.test.okid.io/api/get-verification-image/ver_abc123..." ^ -H "X-SDK-Key: your-api-key-here" ^ -o "document-redacted.jpg"
Windows (PowerShell):
$headers = @{ "X-SDK-Key" = "your-api-key-here" } Invoke-RestMethod -Uri "https://verify.test.okid.io/api/get-verification-image/ver_abc123..." \ -Method GET -Headers $headers -OutFile "document-redacted.jpg"
Unredacted Images (no API key required):
Linux / macOS:
# Get unredacted image (watermark only) curl -X GET "https://verify.test.okid.io/api/get-verification-image-unredacted/ver_abc123..." \ -o "document-unredacted.jpg" # With optional parameters curl -X GET "https://verify.test.okid.io/api/get-verification-image-unredacted/ver_abc123...?attempt_index=0&image_type=original" \ -o "document-unredacted-original.jpg"
Windows (CMD):
curl -X GET "https://verify.test.okid.io/api/get-verification-image-unredacted/ver_abc123..." ^ -o "document-unredacted.jpg"
Windows (PowerShell):
Invoke-RestMethod -Uri "https://verify.test.okid.io/api/get-verification-image-unredacted/ver_abc123..." \ -Method GET -OutFile "document-unredacted.jpg"
Quick cURL Examples
Redacted Image (with API key):
curl -X GET "https://verify.test.okid.io/api/get-verification-image/ver_abc123..." -H "X-SDK-Key: your-api-key-here" -o "document.jpg"
Unredacted Image (with API key):
curl -X GET "https://verify.test.okid.io/api/get-verification-image-unredacted/ver_abc123..." -H "X-SDK-Key: your-api-key-here" -o "document-unredacted.jpg"
Testing Your Integration
Quick Test Checklist:
Troubleshooting Common Issues:
Error: "Invalid API key provided"
Check that your API key is correctly set in environment variables and is active in the v2dashboard.
Error: "API key does not have access to this verification"
You can only retrieve verifications created by your API key. Double-check the verification_id and ensure it was generated using the same API key.
Error: "Server not configured"
The v2client instance needs to be configured with your backend URL and API key. Contact your administrator.
User gets stuck on verification portal
Check browser console for JavaScript errors. Ensure the verification_id is valid and hasn't expired (check expiration in API response).
Ready for Production
Once your test verification completes successfully and you can retrieve the results, your integration is ready for production use!