Integration Guide

Overview

Integrate v2client verification into your application using either:

  • Modal/Iframe Integration - Embed verification in a modal or iframe
  • Direct Integration - Redirect users to the verification portal

Modal/Iframe Integration

1. Include the SDK

<!-- Include the verification SDK -->
<script src="https://verify.stage.okid.io/verification-sdk.js"></script>

<!-- Include modal CSS -->
<link rel="stylesheet" href="https://verify.stage.okid.io/modal.css">

2. Initialize the SDK

// Initialize the SDK
const verificationSDK = new VerificationSDK({
  baseUrl: 'https://verify.stage.okid.io',
  // locale: 'nl',               // Optional: override frontend language ('en', 'nl', 'es', 'de', 'fr')
  // mobileOnly: true,           // Optional: hide desktop webcam, show only QR/phone flow
  // autoCloseOnReview: true,    // Optional: auto-close modal on needs_manual_review (default: false)
  // autoCloseOnRejection: true, // Optional: auto-close modal on rejected/failed (default: false)
  onSuccess: (data) => {
    console.log('Verification completed:', data);
    // data includes: verification_id, status, exported_data, return_url
  },
  onNeedsReview: (data) => {
    console.log('Verification needs review:', data);
    // Called when manual review is needed
    // If autoCloseOnReview is false (default), modal stays open - call forceClose() when ready
  },
  onError: (error) => {
    console.error('Verification failed:', error);
    // If autoCloseOnRejection is false (default), modal stays open showing error
  },
  onClose: () => {
    console.log('Verification modal closed');
  },
  onRetryRequested: (data) => {
    console.log('Retry requested:', data);
    // CRITICAL: Generate new verification ID - failed ones cannot be reused
    generateNewVerificationAndRetry();
  }
});

async function generateNewVerificationAndRetry() {
  // Call your server to generate a new verification ID with your API key
  const response = await fetch('/api/generate-verification', { method: 'POST' });
  const { verificationId } = await response.json();
  
  // Start new verification with fresh ID
  verificationSDK.openWithVerificationId(verificationId);
}

3. Open Verification Modal

Option A: Direct Open (User generates verification)

// Let user generate their own verification
document.getElementById('verify-btn').addEventListener('click', () => {
  verificationSDK.openDirect();
});

Option B: Pre-generated Verification ID

// Use server-generated verification ID
async function startVerification() {
  try {
    // Generate verification ID on your server
    const response = await fetch('/api/generate-verification', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' }
    });
    
    const { verificationId } = await response.json();
    
    // Direct verification (skips selection screen)
    verificationSDK.openWithVerificationId(verificationId);
    
    // OR: Show selection screen (QR code + device choice)
    // verificationSDK.openWithVerificationId(verificationId, true);
  } catch (error) {
    console.error('Failed to generate verification:', error);
  }
}

URL Patterns:
• Direct verification: /?verification_id=...
• Selection screen: /?verification_id=...&mode=select
• Mobile only: /?verification_id=...&mode=select&mobile_only=true
• Override language: /?verification_id=...&locale=nl

Server-side Setup

Generate Verification ID (Required for Option B)

// Example: Node.js/Express endpoint
app.post('/api/generate-verification', async (req, res) => {
  try {
    const response = await fetch('https://verify.stage.okid.io/api/generate-verification', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'X-SDK-Key': process.env.OKID_API_KEY // Store API key securely on server
      },
      body: JSON.stringify({
        // Optional: Attach custom metadata (returned in webhooks)
        extra_data: {
          reference_id: req.body.orderId,
          user_id: req.user.id,
          context: 'account_verification'
        },
        // Optional: Redirect user back to your app after verification
        return_url: 'https://your-app.com/verification-complete'
      })
    });

    if (!response.ok) {
      throw new Error('Failed to generate verification');
    }

    const data = await response.json();
    res.json({ verificationId: data.verificationId });
            
        } catch (error) {
    console.error('Verification generation failed:', error);
    res.status(500).json({ error: 'Failed to generate verification' });
  }
});

Pro Tip: Use the extra_data field to attach custom metadata (order IDs, user references, etc.) to verifications. This data is stored with the verification and included in webhook notifications, making it easy to match verifications with your internal systems.

Direct Integration

For simpler integration, redirect users directly to the verification portal:

Direct Portal Access

// Let user generate verification themselves
window.location.href = 'https://verify.stage.okid.io/';

// Direct verification with pre-generated ID (skips selection)
window.location.href = 'https://verify.stage.okid.io/?verification_id=' + verificationId;

// Selection screen with pre-generated ID (shows QR + device choice)
window.location.href = 'https://verify.stage.okid.io/?verification_id=' + verificationId + '&mode=select';

// Mobile-only flow (hides desktop webcam option, only shows QR/phone)
window.location.href = 'https://verify.stage.okid.io/?verification_id=' + verificationId + '&mode=select&mobile_only=true';

When to use mode=select:
• Desktop-first applications wanting mobile option
• When you want users to choose their verification device
• For QR code scanning experience with desktop animation

When to use mobile_only=true:
• When verification must be performed on a mobile device (e.g. camera quality requirements)
• Hides the desktop webcam option, users can only scan the QR code with their phone
• Can also be configured per API key in the dashboard (Mobile Only Flow toggle)

Verification Outcomes

The verification system produces three terminal statuses:

verified- Identity successfully verified
needs_manual_review- Requires manual review by your team
rejected- Verification failed

About document_expired: When a document's expiration date fails validation, the backend status is rejected with a rejection reason of DOCUMENT_EXPIRED. The client-side SDK surfaces this as a distinct document_expired value in URL query parameters, postMessage events, and SDK callbacks so you can show a specific message to the user. Webhook payloads will contain status: "rejected" with reason: "DOCUMENT_EXPIRED".

Modal Close Behavior

The SDK modal closes automatically only for verified status by default. For other terminal states, you can configure auto-close via SDK options:

StatusDefaultOption to auto-close
verifiedCloses automaticallyAlways
needs_manual_reviewStays openautoCloseOnReview: true
rejectedStays open (shows error)autoCloseOnRejection: true

When the modal stays open, you can close it programmatically from your callback:

const sdk = new VerificationSDK({
  baseUrl: 'https://verify.stage.okid.io',
  onNeedsReview: (data) => {
    // Show your own UI, then close when ready
    showReviewMessage('Your verification is under review.');
    sdk.forceClose();
  }
});

return_url Redirect

When a return_url is provided during verification generation, the user is automatically redirected after verification completes:

Direct/redirect mode: The verification page shows a 3-second countdown, then redirects to the return_url.
SDK/modal mode: The modal closes, then the parent page is redirected to the return_url. The return_url is included in the onSuccess callback data as data.return_url.

Note: In SDK/modal mode, if you handle navigation yourself in onSuccess, the automatic return_url redirect will still fire after the callback. To prevent this, do not pass return_url during generation and handle routing in your callback instead.

WebView / Mobile App Integration

For mobile apps using WebView to display the verification flow, you can detect completion by monitoring URL changes.

URL-Based Completion Detection

When verification completes, the URL is updated with query parameters indicating the outcome. This eliminates the need for API polling.

Completion URL Pattern:
https://verify.stage.okid.io/verify?verification_id=xxx&step=validation&status=complete&verification_status=verified

Query Parameters

status=complete- Indicates verification flow has finished
verification_status- The outcome: verified, rejected, needs_manual_review, document_expired, or error. Note: document_expired is a client-side refinement of rejected (see above)

Important: The URL is updated using JavaScript history.replaceState(), which modifies the URL without triggering a page navigation. This means navigation-based callbacks like shouldOverrideUrlLoading (Android) or decidePolicyFor navigationAction (iOS) will not be triggered. Use the methods below to detect this change.

Android (WebView)

Use onPageFinished to check the URL after the page loads, or doUpdateVisitedHistory to detect replaceState changes:

webView.webViewClient = object : WebViewClient() {
    
    // Method 1: Check URL when page finishes loading
    override fun onPageFinished(view: WebView?, url: String?) {
        super.onPageFinished(view, url)
        url?.let { checkForCompletion(Uri.parse(it)) }
    }
    
    // Method 2: Detect replaceState/pushState changes (API 26+)
    override fun doUpdateVisitedHistory(view: WebView?, url: String?, isReload: Boolean) {
        super.doUpdateVisitedHistory(view, url, isReload)
        url?.let { checkForCompletion(Uri.parse(it)) }
    }
    
    private fun checkForCompletion(url: Uri) {
        if (url.getQueryParameter("status") == "complete") {
            val verificationStatus = url.getQueryParameter("verification_status") ?: "unknown"
            
            // Handle completion
            handleVerificationComplete(verificationStatus)
            
            // Close WebView activity
            finish()
        }
    }
}

iOS (WKWebView)

Use didFinish navigation delegate or KVO on the url property to detect URL changes:

// Method 1: Check URL when navigation finishes
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
    checkForCompletion(webView.url)
}

// Method 2: Observe URL changes using KVO (catches replaceState)
override func observeValue(forKeyPath keyPath: String?, 
                           of object: Any?, 
                           change: [NSKeyValueChangeKey : Any]?, 
                           context: UnsafeMutableRawPointer?) {
    if keyPath == "URL", let webView = object as? WKWebView {
        checkForCompletion(webView.url)
    }
}

// Don't forget to add observer in viewDidLoad:
// webView.addObserver(self, forKeyPath: "URL", options: .new, context: nil)

private func checkForCompletion(_ url: URL?) {
    guard let url = url,
          let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
          components.queryItems?.first(where: { $0.name == "status" })?.value == "complete" else {
        return
    }
    
    let verificationStatus = components.queryItems?
        .first(where: { $0.name == "verification_status" })?.value ?? "unknown"
    
    // Handle completion
    handleVerificationComplete(status: verificationStatus)
    
    // Close WebView
    dismiss(animated: true)
}

Advantage: URL monitoring eliminates the need for API polling, reducing server load and providing instant completion detection.

Retry Handling

When verification fails in SDK/modal context, the system sends a retry_requested message instead of redirecting to the portal.

Critical: Failed verification IDs cannot be reused. You must generate a new verification ID with your API key when handling retry requests.

Why Retry Handling is Needed

  • Users coming from operator integrations used a custom API key to create the verification
  • Simply redirecting to / would lose the operator context
  • The operator must generate a new verification ID to maintain API key association

Implementation Example

// Complete retry handling implementation
const sdk = new VerificationSDK({
  baseUrl: 'https://verify.stage.okid.io',
  onSuccess: (data) => {
    // Verification succeeded
    handleVerificationSuccess(data);
  },
  onRetryRequested: (data) => {
    console.log('Verification failed, retry requested:', data.reason);
    
    // Show loading state to user
    showRetryMessage('Generating new verification...');
    
    // Generate new verification ID server-side
    generateNewVerificationAndRetry();
  }
});

async function generateNewVerificationAndRetry() {
  try {
    // Call your server endpoint that uses your API key securely
    const response = await fetch('/api/generate-verification', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' }
    });
    
    if (!response.ok) {
      throw new Error('Failed to generate new verification');
    }
    
    const { verificationId } = await response.json();
    
    // Start new verification with fresh ID
    sdk.openWithVerificationId(verificationId);
    
  } catch (error) {
    console.error('Retry failed:', error);
    showErrorMessage('Unable to start new verification. Please try again.');
  }
}

function showRetryMessage(message) {
  // Show user-friendly message during retry
  document.getElementById('status').textContent = message;
}

function showErrorMessage(message) {
  // Show error if retry fails
  document.getElementById('error').textContent = message;
}

Troubleshooting

PostMessage events not triggering?

  • Check browser console for iframe loading errors
  • Verify the baseUrl is correct
  • Ensure verification completes successfully
  • Check for console logs starting with [IFRAME]

Verification data not loading in iframe?

  • Use /?verification_id=... for direct verification
  • Use /?verification_id=...&mode=select only if you want the selection screen
  • Ensure the verification_id was generated recently
  • Check that your API key has the correct permissions

onRetryRequested not being called?

  • Ensure you're using the latest version of verification-sdk.js
  • Check browser console for [SDK] Retry requested messages
  • Verify the callback is properly set in SDK initialization
  • Make sure verification actually failed (not just closed by user)