15 min integration

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 ─────────▶│

1
Get 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:

  1. Log into your v2dashboard
  2. Navigate to the "API Keys" section
  3. Click "Create API Key"
  4. Configure verification modules (document, liveness, form_data)
  5. Set retention period and other settings
  6. 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

2
Generate 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']);
    }
}
?>

3
User Verification Flow

After redirecting the user to the v2client portal, they will complete the verification process automatically.

What Happens Next:

  1. User lands on the v2client verification portal
  2. Portal validates the verification ID
  3. User completes verification modules (terms, document, liveness, forms)
  4. Results are processed and stored
  5. User sees completion status
  6. 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!

4
Retrieve 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.

GET /api/get-verification-image/ver_123 X-SDK-Key: your-api-key-here
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.

GET /api/get-verification-image-unredacted/ver_123 X-SDK-Key: your-api-key-here
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.

GET /api/get-verification-image-front/ver_123 X-SDK-Key: your-api-key-here
Back Image

/api/get-verification-image-back/{verification_id}

Automatically finds the accepted back document image. No need to determine attempt indices.

GET /api/get-verification-image-back/ver_123 X-SDK-Key: your-api-key-here
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:

API key created and stored securely
Server can make requests to v2client generate-verification endpoint
Verification ID is generated and user is redirected correctly
User can complete verification flow in v2client portal
Server can retrieve verification results using API key
Results appear in your v2dashboard

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!