Dashboard

Troubleshooting Knowledge Base

Comprehensive troubleshooting guide for VerifyHuman platform integration, covering all products, integration methods, and common issues.

Table of Contents


Quick Diagnostics

Diagnostic Checklist

Before diving into specific issues, run through this quick checklist:

Check How to Verify Common Fix
API Key Valid Key starts with vhk- prefix Generate new key in dashboard
API Key Active Dashboard shows key as "Active" Enable or regenerate key
Product Scopes Key has required product scope enabled Edit key scopes in dashboard
Sufficient Credits Dashboard → Credits shows positive balance Add credits or upgrade plan
Domain Whitelisted Dashboard → API Keys → Domain Whitelist Add your domain
HTTPS Enabled Site uses https:// Enable SSL certificate
Correct Endpoint Using product-specific endpoint Check API documentation

Service Status

Check current API status and any ongoing incidents:

Test Your API Key

curl -X POST https://app.verifyhuman.io/api/verify \
  -F "clientKey=vhk-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6" \
  -F "file=@test-selfie.jpg"

API Integration Issues

Error: "Invalid API Key"

Symptoms:

{
  "error": "Invalid API key",
  "status": "ERROR"
}

Root Causes:

  1. Wrong API Key Format

    • Each product requires its specific key prefix
    • VerifyHuman: vhk-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6 or vhk-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
    • VerifyAge: vhk-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6 or vhk-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
    • VerifyIdentity: vhk-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6 or vhk-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
    • VerifyTrust: vhk-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
  2. Key Revoked or Expired

    • Check key status in dashboard
    • Keys can be revoked manually or due to security policies
  3. Environment Mismatch

    • Test keys don't work in production
    • Live keys shouldn't be used for testing

Solutions:

# WRONG - missing prefix
headers = {'Authorization': 'Bearer abc123xyz'}

# WRONG - wrong product prefix
api_key = 'vhk-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6'  # Using VerifyHuman key for VerifyAge
response = requests.post('/api/v1/verify-age/init', ...)

# CORRECT - matching product and key
api_key = 'vhk-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6'  # VerifyAge key for VerifyAge endpoint
response = requests.post('/api/v1/verify-age/init', 
    data={'apiKey': api_key})

Verification Script:

import requests

def verify_api_key(api_key):
    """Test if API key is valid and check its properties"""
    # Determine product from prefix
    prefix = api_key.split('_')[0] if '_' in api_key else 'unknown'

    endpoints = {
        'vhk': '/api/verify',
        'vage': '/api/v1/verify-age/init',
        'kyc': '/api/identity/v1/submit',
        'ident': '/api/identity/v1/submit',
        'kycp': '/api/kyc/v1/submit_plus'
    }

    if prefix not in endpoints:
        return {'valid': False, 'error': f'Unknown prefix: {prefix}'}

    # Perform a minimal validation request
    # (actual verification requires proper payload)
    return {'valid': True, 'product': prefix, 'endpoint': endpoints[prefix]}

# Usage
result = verify_api_key('vhk-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6')
print(result)

Error: "Insufficient Credits"

Symptoms:

{
  "error": "Insufficient credits",
  "credits_required": 30,
  "credits_available": 15
}

Credit Costs by Product:

Product Credits per Verification
VerifyHuman 1 credit
VerifyAge 10 credits
VerifyIdentity 30 credits
VerifyTrust 50 credits

Solutions:

  1. Check Current Balance:

    • Log in to your Dashboard
    • View credit balance in the sidebar or Credits page
  2. Set Up Low Credit Alerts:

    • Dashboard → Settings → Notifications
    • Enable "Low Credit Warning" alerts
    • Set threshold (e.g., alert when below 100 credits)
  3. Purchase Options:

    • Upgrade subscription plan in dashboard
    • Purchase prepaid credit packages
    • Enable auto-refill (Pro/Enterprise plans)

Error: "Rate Limit Exceeded"

Symptoms:

{
  "error": "Rate limit exceeded",
  "retry_after": 60,
  "limit": "60 requests/minute"
}

Rate Limits by Plan:

Plan Requests/Minute Verifications/Month
Free 10 20
Starter 60 1,000
Growth 300 10,000
Pro 500 50,000
Enterprise 1,000 Unlimited

Solutions:

  1. Implement Exponential Backoff:
async function verifyWithRetry(file, maxRetries = 5) {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await verify(file);
    } catch (error) {
      if (error.status === 429) {
        const retryAfter = error.headers?.['Retry-After'] || Math.pow(2, attempt);
        console.log(`Rate limited. Waiting ${retryAfter} seconds...`);
        await sleep(retryAfter * 1000);
        continue;
      }
      throw error;
    }
  }
  throw new Error('Max retries exceeded');
}

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}
  1. Implement Request Queuing:
import time
from collections import deque
from threading import Lock

class RateLimiter:
    def __init__(self, max_requests, window_seconds):
        self.max_requests = max_requests
        self.window_seconds = window_seconds
        self.requests = deque()
        self.lock = Lock()

    def acquire(self):
        with self.lock:
            now = time.time()
            # Remove old requests outside window
            while self.requests and self.requests[0] < now - self.window_seconds:
                self.requests.popleft()

            if len(self.requests) >= self.max_requests:
                wait_time = self.requests[0] + self.window_seconds - now
                time.sleep(wait_time)
                return self.acquire()

            self.requests.append(now)
            return True

# Usage
limiter = RateLimiter(max_requests=60, window_seconds=60)
limiter.acquire()
response = verify_api_call()
  1. Cache Verification Results:
from functools import lru_cache
import hashlib

@lru_cache(maxsize=1000)
def get_cached_verification(image_hash, user_id):
    """Cache verifications for the same user/image combination"""
    # Only hits API if not in cache
    return perform_verification(image_hash, user_id)

def verify_user(image_data, user_id):
    # Create hash of image for caching
    image_hash = hashlib.sha256(image_data).hexdigest()
    return get_cached_verification(image_hash, user_id)

Error: "File Too Large"

Symptoms:

{
  "error": "File size exceeds maximum allowed (10MB)",
  "file_size": "15.2MB",
  "max_size": "10MB"
}

File Requirements:

Requirement Value
Max File Size 10 MB
Supported Formats JPG, JPEG, PNG, WebP
Recommended Resolution 1920x1080 or less
Minimum Face Size 100x100 pixels

Solutions:

  1. Client-Side Compression (JavaScript):
async function compressImage(file, options = {}) {
  const {
    maxSizeMB = 5,
    maxWidth = 1920,
    maxHeight = 1080,
    quality = 0.85
  } = options;

  // Skip if already small enough
  if (file.size <= maxSizeMB * 1024 * 1024) {
    return file;
  }

  return new Promise((resolve, reject) => {
    const img = new Image();
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');

    img.onload = () => {
      let { width, height } = img;

      // Calculate new dimensions
      if (width > maxWidth) {
        height = (height / width) * maxWidth;
        width = maxWidth;
      }
      if (height > maxHeight) {
        width = (width / height) * maxHeight;
        height = maxHeight;
      }

      canvas.width = width;
      canvas.height = height;
      ctx.drawImage(img, 0, 0, width, height);

      canvas.toBlob(
        (blob) => {
          if (blob) {
            resolve(new File([blob], file.name, { type: 'image/jpeg' }));
          } else {
            reject(new Error('Failed to compress image'));
          }
        },
        'image/jpeg',
        quality
      );
    };

    img.onerror = () => reject(new Error('Failed to load image'));
    img.src = URL.createObjectURL(file);
  });
}

// Usage
const compressedFile = await compressImage(originalFile);
formData.append('file', compressedFile);
  1. Server-Side Compression (Python):
from PIL import Image
import io

def compress_image(image_data, max_size_mb=5, quality=85):
    """Compress image to meet size requirements"""
    img = Image.open(io.BytesIO(image_data))

    # Convert to RGB if necessary (for PNG with transparency)
    if img.mode in ('RGBA', 'P'):
        img = img.convert('RGB')

    # Resize if too large
    max_dimension = 1920
    if max(img.size) > max_dimension:
        ratio = max_dimension / max(img.size)
        new_size = tuple(int(dim * ratio) for dim in img.size)
        img = img.resize(new_size, Image.LANCZOS)

    # Compress with decreasing quality until under max size
    output = io.BytesIO()
    current_quality = quality

    while current_quality > 10:
        output.seek(0)
        output.truncate()
        img.save(output, format='JPEG', quality=current_quality, optimize=True)

        if output.tell() <= max_size_mb * 1024 * 1024:
            break
        current_quality -= 10

    return output.getvalue()
  1. Node.js Compression:
const sharp = require('sharp');

async function compressImage(buffer) {
  return await sharp(buffer)
    .resize(1920, 1080, { fit: 'inside', withoutEnlargement: true })
    .jpeg({ quality: 85, progressive: true })
    .toBuffer();
}

Error: "Domain Not Whitelisted"

Symptoms:

{
  "error": "API key restricted to whitelisted domains",
  "your_domain": "unauthorized-site.com",
  "whitelisted_domains": ["example.com", "app.example.com"]
}

Solutions:

  1. Add Domain in Dashboard:

    Dashboard → API Keys → [Your Key] → Domain Whitelist → Add Domain
    
  2. Domain Format Requirements:

    • Include protocol: https://yoursite.com
    • Add both www and non-www versions
    • Add subdomains separately: app.yoursite.com
    • For localhost testing: http://localhost:3000
  3. Wildcard Domains (Enterprise only):

    *.yoursite.com  // Matches all subdomains
    
  4. Check Current Whitelist:

    • Dashboard → API Keys → [Your Key] → Domain Whitelist
    • View all currently whitelisted domains

Error: "Invalid File Format"

Symptoms:

{
  "error": "Unsupported file format",
  "received": "image/gif",
  "supported": ["image/jpeg", "image/png", "image/webp"]
}

Solutions:

  1. Validate Before Upload:
const SUPPORTED_FORMATS = ['image/jpeg', 'image/jpg', 'image/png', 'image/webp'];

function validateFile(file) {
  if (!SUPPORTED_FORMATS.includes(file.type)) {
    throw new Error(`Unsupported format: ${file.type}. Use JPG, PNG, or WebP.`);
  }
  return true;
}
  1. Convert to Supported Format:
async function convertToJpeg(file) {
  const canvas = document.createElement('canvas');
  const ctx = canvas.getContext('2d');
  const img = await createImageBitmap(file);

  canvas.width = img.width;
  canvas.height = img.height;
  ctx.drawImage(img, 0, 0);

  return new Promise(resolve => {
    canvas.toBlob(blob => {
      resolve(new File([blob], 'photo.jpg', { type: 'image/jpeg' }));
    }, 'image/jpeg', 0.92);
  });
}

Error: "Request Timeout"

Symptoms:

{
  "error": "Request timeout",
  "timeout": 30000,
  "message": "Verification processing exceeded time limit"
}

Solutions:

  1. Increase Client Timeout:
import requests

response = requests.post(
    'https://app.verifyhuman.io/api/verify',
    files={'file': image_file},
    data={'clientKey': API_KEY},
    timeout=(10, 60)  # (connect timeout, read timeout)
)
  1. Use Async Processing for Large Batches:
import asyncio
import aiohttp

async def verify_async(session, image_data, api_key):
    async with session.post(
        'https://app.verifyhuman.io/api/verify',
        data={'clientKey': api_key, 'file': image_data},
        timeout=aiohttp.ClientTimeout(total=60)
    ) as response:
        return await response.json()

async def batch_verify(images, api_key):
    async with aiohttp.ClientSession() as session:
        tasks = [verify_async(session, img, api_key) for img in images]
        return await asyncio.gather(*tasks, return_exceptions=True)
  1. Implement Retry with Status Polling:
async function verifyWithPolling(file, apiKey) {
  // Submit verification
  const submitResponse = await fetch('/api/verify', {
    method: 'POST',
    body: createFormData(file, apiKey)
  });

  const { verification_id } = await submitResponse.json();

  // Poll for result
  for (let i = 0; i < 30; i++) {
    await sleep(1000);

    const statusResponse = await fetch(`/api/status/${verification_id}`);
    const status = await statusResponse.json();

    if (status.status !== 'PROCESSING') {
      return status;
    }
  }

  throw new Error('Verification timeout');
}

Widget Integration Issues

Widget Not Loading

Symptoms:

Diagnostic Steps:

  1. Check Script Loading:
// Add to browser console
console.log('VerifyHuman object:', typeof VerifyHuman);
// Should output: 'VerifyHuman object: object'
  1. Verify Container Exists:
console.log('Container:', document.getElementById('verifyhuman-widget'));
// Should output the DOM element, not null

Solutions:

  1. Correct Script Ordering:
<!-- WRONG - script runs before container exists -->
<script>
  VerifyHuman.init({ apiKey: 'vhk-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6' });
</script>
<div id="verifyhuman-widget"></div>

<!-- CORRECT - container first, then script -->
<div id="verifyhuman-widget"></div>
<script src="https://app.verifyhuman.io/widget.js"></script>
<script>
  VerifyHuman.init({ apiKey: 'vhk-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6' });
</script>

<!-- BEST - wait for DOM ready -->
<script src="https://app.verifyhuman.io/widget.js"></script>
<script>
  document.addEventListener('DOMContentLoaded', function() {
    VerifyHuman.init({ apiKey: 'vhk-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6' });
  });
</script>
  1. React Integration:
import { useEffect, useRef } from 'react';

function VerifyHumanWidget({ apiKey, onSuccess, onFailure }) {
  const containerRef = useRef(null);
  const initialized = useRef(false);

  useEffect(() => {
    // Only initialize once
    if (initialized.current) return;

    // Load script dynamically
    const script = document.createElement('script');
    script.src = 'https://app.verifyhuman.io/widget.js';
    script.async = true;

    script.onload = () => {
      if (window.VerifyHuman && containerRef.current) {
        window.VerifyHuman.init({
          apiKey,
          container: containerRef.current,
          onSuccess,
          onFailure
        });
        initialized.current = true;
      }
    };

    document.head.appendChild(script);

    return () => {
      // Cleanup on unmount
      if (window.VerifyHuman?.destroy) {
        window.VerifyHuman.destroy();
      }
    };
  }, [apiKey, onSuccess, onFailure]);

  return <div ref={containerRef} id="verifyhuman-widget" />;
}
  1. Vue Integration:
<template>
  <div ref="widgetContainer" id="verifyhuman-widget"></div>
</template>

<script>
export default {
  props: ['apiKey'],
  mounted() {
    this.loadWidget();
  },
  methods: {
    loadWidget() {
      const script = document.createElement('script');
      script.src = 'https://app.verifyhuman.io/widget.js';
      script.onload = () => {
        window.VerifyHuman.init({
          apiKey: this.apiKey,
          container: this.$refs.widgetContainer,
          onSuccess: this.handleSuccess,
          onFailure: this.handleFailure
        });
      };
      document.head.appendChild(script);
    },
    handleSuccess(result) {
      this.$emit('verification-success', result);
    },
    handleFailure(error) {
      this.$emit('verification-failure', error);
    }
  }
}
</script>

Camera Access Denied

Symptoms:

Root Causes:

  1. HTTP Instead of HTTPS
  2. Browser Permissions Blocked
  3. Another App Using Camera
  4. Privacy Settings

Solutions:

  1. HTTPS Requirement:
✗ http://example.com (camera will NOT work)
✓ https://example.com (camera works)
✓ http://localhost:3000 (allowed for development)
✓ http://127.0.0.1:8080 (allowed for development)
  1. Handle Permission Errors:
VerifyHuman.init({
  apiKey: 'vhk-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6',
  onCameraError: function(error) {
    switch(error.name) {
      case 'NotAllowedError':
        showMessage('Please allow camera access in your browser settings.');
        break;
      case 'NotFoundError':
        showMessage('No camera found. Please connect a camera and try again.');
        break;
      case 'NotReadableError':
        showMessage('Camera is in use by another application. Please close other apps using the camera.');
        break;
      case 'OverconstrainedError':
        showMessage('Camera does not meet requirements. Please try a different camera.');
        break;
      default:
        showMessage('Camera error: ' + error.message);
    }
  }
});
  1. Browser-Specific Permission Reset:

Chrome:

1. Click lock icon in address bar
2. Click "Site settings"
3. Find "Camera" and set to "Allow"
4. Refresh the page

Firefox:

1. Click lock icon in address bar
2. Click "Connection secure" → "More Information"
3. Go to "Permissions" tab
4. Find "Use the Camera" and select "Allow"

Safari:

1. Safari → Preferences → Websites → Camera
2. Find your site and set to "Allow"
  1. Provide Fallback for File Upload:
VerifyHuman.init({
  apiKey: 'vhk-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6',
  captureMode: 'auto', // Try camera first, fallback to file
  allowFileUpload: true,
  onCameraError: function(error) {
    console.log('Camera unavailable, using file upload');
  }
});

Widget Styling Issues

Symptoms:

Solutions:

  1. Fix Z-Index Conflicts:
/* Ensure widget is above other elements */
#verifyhuman-widget {
  position: relative;
  z-index: 1000;
}

/* If using modal dialogs */
.verifyhuman-modal {
  z-index: 10000 !important;
}
  1. Container Sizing:
#verifyhuman-widget {
  width: 100%;
  max-width: 500px;
  min-height: 400px;
  margin: 0 auto;
}

/* Responsive sizing */
@media (max-width: 768px) {
  #verifyhuman-widget {
    max-width: 100%;
    min-height: 350px;
  }
}
  1. Custom Theming:
VerifyHuman.init({
  apiKey: 'vhk-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6',
  theme: 'dark',
  customStyles: {
    primaryColor: '#4F46E5',
    backgroundColor: '#1F2937',
    textColor: '#F9FAFB',
    borderRadius: '12px',
    fontFamily: 'Inter, system-ui, sans-serif'
  }
});
  1. Fix CSS Conflicts:
/* Reset potential conflicts */
#verifyhuman-widget * {
  box-sizing: border-box;
}

#verifyhuman-widget button {
  all: unset;
  /* Apply widget-specific styles */
}

CORS Errors

Symptoms:

Access to fetch at 'https://app.verifyhuman.io/api/verify' from origin 
'https://yoursite.com' has been blocked by CORS policy

Understanding CORS:

Solutions:

  1. Use Widget for Frontend:
// WRONG - Direct API call from browser
fetch('https://app.verifyhuman.io/api/verify', {
  method: 'POST',
  body: formData
});

// CORRECT - Use the widget
VerifyHuman.init({
  apiKey: 'vhk-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6',
  onSuccess: (result) => {
    // Send token to your backend
    sendToBackend(result.token);
  }
});
  1. Proxy Through Your Backend:
# Flask backend proxy
@app.route('/api/verify', methods=['POST'])
def verify_proxy():
    # Receive file from frontend
    file = request.files['file']

    # Forward to VerifyHuman API
    response = requests.post(
        'https://app.verifyhuman.io/api/verify',
        files={'file': file},
        data={'clientKey': os.environ['VERIFYHUMAN_API_KEY']}
    )

    return jsonify(response.json())
  1. Node.js Proxy:
const express = require('express');
const multer = require('multer');
const fetch = require('node-fetch');
const FormData = require('form-data');

const upload = multer();

app.post('/api/verify', upload.single('file'), async (req, res) => {
  const formData = new FormData();
  formData.append('clientKey', process.env.VERIFYHUMAN_API_KEY);
  formData.append('file', req.file.buffer, req.file.originalname);

  const response = await fetch('https://app.verifyhuman.io/api/verify', {
    method: 'POST',
    body: formData
  });

  const result = await response.json();
  res.json(result);
});

Hosted Flows Issues

Symptoms:

Solutions:

  1. Verify Complete URL:
✗ https://verify.verifyhuman.io/flow/abc123 (incomplete)
✓ https://verify.verifyhuman.io/flow/vh_abc123xyz789 (complete with prefix)
  1. Check Flow Status:

    • Login to Dashboard → Hosted Flows
    • Verify flow shows as "Active"
    • Check expiration date
  2. Common URL Issues:

    • Extra spaces when copying
    • Missing or incomplete flow ID
    • Typos in flow ID
  3. Test Flow Programmatically:

curl -I "https://verify.verifyhuman.io/flow/vh_abc123xyz789"
# Look for 200 OK, not 404 Not Found

Redirect Not Working

Symptoms:

Solutions:

  1. Check Redirect Configuration:

    • Dashboard → Hosted Flows → [Your Flow] → Redirect Settings
    • Success URL must include https://
    • Test URL is accessible
  2. URL Parameter Placeholders:

Success URL: https://yoursite.com/verified?id={VERIFICATION_ID}&status={STATUS}
Failure URL: https://yoursite.com/failed?error={ERROR}&id={VERIFICATION_ID}

Available placeholders:

Placeholder Description
{VERIFICATION_ID} Unique verification identifier
{STATUS} PASS, FAIL, or ERROR
{CONFIDENCE} Confidence score (0-100)
{TOKEN} JWT verification token
{ERROR} Error message (failure only)
  1. Handle Redirect on Your Server:
@app.route('/verified')
def verification_complete():
    verification_id = request.args.get('id')
    status = request.args.get('status')

    if status == 'PASS':
        # Store verification in database
        save_verification(verification_id)
        return render_template('success.html')
    else:
        return render_template('retry.html')

Custom Branding Not Displaying

Symptoms:

Solutions:

  1. Logo Requirements:

    Requirement Specification
    Format PNG or SVG
    Max Size 500 KB
    Recommended Dimensions 300x100 pixels
    Background Transparent recommended
    Color Mode RGB
  2. Color Format:

✗ "blue"           (won't work)
✗ "rgb(0,0,255)"   (won't work)
✓ "#0000FF"        (correct)
✓ "#00f"           (correct shorthand)
  1. Cache Issues:

    • Wait 24 hours for CDN cache to clear
    • Or append cache-buster: ?v=timestamp
    • Force refresh: Ctrl+Shift+R / Cmd+Shift+R
  2. Check Configuration in Dashboard:

    • Dashboard → Hosted Flows → [Your Flow] → Branding Settings
    • Verify logo and colors are saved correctly
    • Re-upload if settings appear blank

Product-Specific Troubleshooting

VerifyHuman Issues

Low Confidence Scores

Symptoms:

{
  "status": "PASS",
  "confidence": 62,
  "warning": "Low confidence score"
}

Causes and Solutions:

  1. Poor Lighting:

    • Front-facing light source
    • Avoid backlight/silhouette
    • Natural daylight preferred
  2. Face Positioning:

VerifyHuman.init({
  apiKey: 'vhk-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6',
  instructions: [
    'Face the camera directly',
    'Keep your face centered in the frame',
    'Fill at least 1/3 of the frame',
    'Remove glasses, hats, or masks'
  ],
  minFaceSize: 0.3 // 30% of frame
});
  1. Implement Confidence Thresholds:
function handleVerification(result) {
  if (result.confidence >= 90) {
    // High confidence - auto approve
    approveUser();
  } else if (result.confidence >= 70) {
    // Medium confidence - accept with note
    approveUser({ requireReview: true });
  } else if (result.confidence >= 50) {
    // Low confidence - request retry
    showRetryPrompt('Please try again with better lighting');
  } else {
    // Very low - likely fraud attempt
    flagForReview(result.verification_id);
  }
}

No Face Detected

Symptoms:

{
  "status": "FAIL",
  "error": "No face detected in image",
  "confidence": 0
}

Solutions:

  1. User Guidance Component:
<div class="verification-tips">
  <h4>For Best Results:</h4>
  <ul>
    <li>✅ Face camera directly</li>
    <li>✅ Good front lighting</li>
    <li>✅ Plain background</li>
    <li>✅ Remove sunglasses/masks</li>
    <li>❌ Avoid backlighting</li>
    <li>❌ Don't use side profile</li>
  </ul>
</div>
  1. Pre-Check Before Submit:
async function preCheckImage(imageFile) {
  // Use local face detection before API call
  const faceDetector = new FaceDetector();
  const faces = await faceDetector.detect(imageFile);

  if (faces.length === 0) {
    throw new Error('No face detected. Please ensure your face is visible and well-lit.');
  }
  if (faces.length > 1) {
    throw new Error('Multiple faces detected. Please ensure only you are in the frame.');
  }

  return true;
}

Liveness Check Failed

Symptoms:

{
  "status": "FAIL",
  "error": "Liveness check failed - possible photo of photo",
  "liveness_score": 35
}

Causes:

Solutions:

  1. Force Camera Capture:
VerifyHuman.init({
  apiKey: 'vhk-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6',
  captureMode: 'camera', // Disable file upload
  requireLiveness: true,
  minLivenessScore: 70
});
  1. Add Liveness Instructions:
<div class="liveness-notice">
  <p><strong>Real-time verification required</strong></p>
  <p>Please use your device's camera for live capture. 
     Photos of photos or screens will not be accepted.</p>
</div>

VerifyAge Issues

Age Estimation Incorrect

Symptoms:

Solutions:

  1. Use Two-Step Verification:
VerifyAge.init({
  apiKey: 'vhk-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6',
  minAge: 21,
  useCase: 'alcohol',
  fallbackToId: true, // Auto-request ID if face estimate is borderline
  borderlineRange: 5   // Years margin for ID fallback
});
  1. Handle Borderline Cases:
def handle_age_verification(result):
    estimated_age = result.get('estimated_age')
    confidence = result.get('confidence')
    min_age = 21

    if estimated_age is None:
        return require_id_verification()

    # Clear pass
    if estimated_age >= min_age + 5 and confidence >= 80:
        return approve_user()

    # Clear fail
    if estimated_age < min_age - 3 and confidence >= 80:
        return reject_user()

    # Borderline - require ID
    return require_id_verification()

State-Specific Requirements

Symptoms:

Solutions:

# US State Age Requirements
STATE_REQUIREMENTS = {
    'alcohol': {
        'default': 21,
        # All US states are 21 for alcohol
    },
    'tobacco': {
        'default': 21,
        'AL': 19,  # Alabama
        'AK': 19,  # Alaska
        # Federal law now 21, but some grandfathering
    },
    'cannabis': {
        'default': 21,
        'CO': 21,
        'CA': 21,
        'WA': 21,
        # Only legal in certain states
    },
    'gambling': {
        'default': 21,
        'MT': 18,
        'OK': 18,
        'WY': 18,
    }
}

def get_age_requirement(use_case, state):
    case_requirements = STATE_REQUIREMENTS.get(use_case, {})
    return case_requirements.get(state, case_requirements.get('default', 21))

VerifyIdentity Issues

ID Document Not Recognized

Symptoms:

{
  "status": "FAIL",
  "error": "Unable to extract data from document",
  "document_quality": "poor"
}

Solutions:

  1. Document Requirements:
<div class="id-requirements">
  <h4>ID Document Guidelines:</h4>
  <ul>
    <li>Valid government-issued photo ID</li>
    <li>Not expired</li>
    <li>All corners visible</li>
    <li>No glare or reflections</li>
    <li>High resolution (at least 1000px wide)</li>
    <li>Color photo (not black & white)</li>
  </ul>

  <h4>Supported Documents:</h4>
  <ul>
    <li>Driver's License</li>
    <li>State ID Card</li>
    <li>Passport</li>
    <li>Passport Card</li>
    <li>Military ID</li>
  </ul>
</div>
  1. Pre-Validation:
function validateIdImage(file) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.onload = () => {
      // Check minimum resolution
      if (img.width < 1000 || img.height < 600) {
        reject(new Error('Image resolution too low. Please capture a higher quality photo.'));
        return;
      }

      // Check aspect ratio (ID cards are typically ~1.586:1)
      const aspectRatio = img.width / img.height;
      if (aspectRatio < 1.3 || aspectRatio > 2.0) {
        reject(new Error('Please capture the full ID card with all corners visible.'));
        return;
      }

      resolve(true);
    };
    img.src = URL.createObjectURL(file);
  });
}

Face Match Failed

Symptoms:

{
  "status": "FAIL",
  "error": "Selfie does not match ID photo",
  "match_confidence": 45
}

Causes:

Solutions:

  1. Matching Guidelines:
<div class="matching-tips">
  <h4>For Best Match Results:</h4>
  <ul>
    <li>Take selfie in similar conditions to ID photo</li>
    <li>Remove accessories not worn in ID (glasses, hat)</li>
    <li>Use good lighting</li>
    <li>Face camera directly</li>
  </ul>
</div>
  1. Handle Low Match Scores:
def handle_face_match(result):
    match_score = result.get('match_confidence', 0)

    if match_score >= 90:
        return approve_verification()
    elif match_score >= 70:
        # Possible match, flag for review
        return flag_for_manual_review(result)
    elif match_score >= 50:
        # Retry with better photo
        return request_retry("Please retake your selfie with better lighting")
    else:
        # Potential fraud
        return flag_security_review(result)

VerifyTrust Issues

AML Screening Hit

Symptoms:

{
  "status": "REVIEW",
  "compliance_screening": {
    "total_hits": 2,
    "sanctions_hits": 0,
    "pep_hits": 1,
    "adverse_media_hits": 1
  }
}

Solutions:

  1. Handle Different Hit Types:
def handle_compliance_result(result):
    screening = result.get('compliance_screening', {})

    # Immediate reject for sanctions
    if screening.get('sanctions_hits', 0) > 0:
        return reject_with_reason("sanctions_match")

    # Review required for PEP
    if screening.get('pep_hits', 0) > 0:
        return flag_for_enhanced_due_diligence(result)

    # Adverse media - depends on severity
    if screening.get('adverse_media_hits', 0) > 0:
        return flag_for_manual_review(result)

    # No hits
    if screening.get('total_hits', 0) == 0:
        return approve_verification()
  1. Access Compliance Reports:
    • Dashboard → Verifications → [Select Verification]
    • Click "Download Report" for AES-256 encrypted PDF
    • Use the report password (provided during verification setup) to decrypt

Plan Access Restrictions

Symptoms:

{
  "error": "VerifyTrust requires Pro or Enterprise plan",
  "current_plan": "Growth"
}

Solution:


SDK-Specific Troubleshooting

Python SDK Issues

SSL Certificate Errors

Symptoms:

requests.exceptions.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED]

Solutions:

  1. Update Certificates:
pip install --upgrade certifi
python -c "import certifi; print(certifi.where())"
  1. Install CA Certificates (Linux):
# Debian/Ubuntu
sudo apt-get update && sudo apt-get install ca-certificates

# CentOS/RHEL
sudo yum install ca-certificates
  1. Temporary Workaround (NOT for production):
import requests

# ONLY for debugging - never use in production
response = requests.post(url, verify=False)

Async/Await Issues

Symptoms:

Solutions:

import asyncio
import aiohttp

# WRONG - nested event loops
async def verify():
    return asyncio.run(do_verification())  # This will fail

# CORRECT - proper async structure
async def verify():
    async with aiohttp.ClientSession() as session:
        return await do_verification(session)

# For Jupyter notebooks
import nest_asyncio
nest_asyncio.apply()

Memory Issues with Large Files

Solutions:

def stream_upload(file_path, api_key):
    """Stream large files to avoid memory issues"""
    with open(file_path, 'rb') as f:
        # Use streaming upload
        response = requests.post(
            'https://app.verifyhuman.io/api/verify',
            files={'file': ('image.jpg', f, 'image/jpeg')},
            data={'clientKey': api_key},
            stream=True
        )
    return response.json()

Node.js SDK Issues

Module Import Errors

Symptoms:

Error: Cannot find module 'verifyhuman'
SyntaxError: Cannot use import statement outside a module

Solutions:

  1. CommonJS vs ES Modules:
// CommonJS (default for Node.js < 14)
const VerifyHuman = require('verifyhuman');

// ES Modules (package.json: "type": "module")
import VerifyHuman from 'verifyhuman';
  1. Package.json Configuration:
{
  "type": "module",
  "dependencies": {
    "verifyhuman": "^2.0.0"
  }
}

File Upload Issues

Symptoms:

Solutions:

const fs = require('fs');
const FormData = require('form-data');
const fetch = require('node-fetch');

async function uploadFile(filePath, apiKey) {
  const form = new FormData();

  // CORRECT - stream from file
  form.append('file', fs.createReadStream(filePath));
  form.append('clientKey', apiKey);

  const response = await fetch('https://app.verifyhuman.io/api/verify', {
    method: 'POST',
    body: form,
    headers: form.getHeaders() // Important!
  });

  return response.json();
}

Memory Leaks

Solutions:

const { Readable } = require('stream');

async function processWithCleanup(imageBuffer) {
  let response;
  try {
    response = await verify(imageBuffer);
    return response;
  } finally {
    // Clean up buffer
    imageBuffer = null;
    if (global.gc) {
      global.gc(); // Force garbage collection if available
    }
  }
}

PHP SDK Issues

cURL Errors

Symptoms:

cURL error 60: SSL certificate problem
cURL error 28: Operation timed out

Solutions:

  1. SSL Certificate Fix:
<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://app.verifyhuman.io/api/verify');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($ch, CURLOPT_CAINFO, '/path/to/cacert.pem');
  1. Download Latest CA Bundle:
curl -o /etc/ssl/certs/cacert.pem https://curl.se/ca/cacert.pem
  1. Timeout Configuration:
<?php
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 10);
curl_setopt($ch, CURLOPT_TIMEOUT, 60);

File Upload Issues

Solutions:

<?php
function verifyImage($filePath, $apiKey) {
    $ch = curl_init();

    $postFields = [
        'clientKey' => $apiKey,
        'file' => new CURLFile($filePath, 'image/jpeg', 'photo.jpg')
    ];

    curl_setopt_array($ch, [
        CURLOPT_URL => 'https://app.verifyhuman.io/api/verify',
        CURLOPT_POST => true,
        CURLOPT_POSTFIELDS => $postFields,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER => ['Content-Type: multipart/form-data']
    ]);

    $response = curl_exec($ch);

    if (curl_errno($ch)) {
        throw new Exception('cURL error: ' . curl_error($ch));
    }

    curl_close($ch);
    return json_decode($response, true);
}

Mobile Integration Issues

iOS Issues

Camera Permission Not Requested

Solutions:

  1. Info.plist Configuration:
<key>NSCameraUsageDescription</key>
<string>Camera access is required for identity verification</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>Photo library access is needed to select verification photos</string>
  1. Request Permission Properly:
import AVFoundation

func requestCameraPermission(completion: @escaping (Bool) -> Void) {
    switch AVCaptureDevice.authorizationStatus(for: .video) {
    case .authorized:
        completion(true)
    case .notDetermined:
        AVCaptureDevice.requestAccess(for: .video) { granted in
            DispatchQueue.main.async {
                completion(granted)
            }
        }
    case .denied, .restricted:
        completion(false)
    @unknown default:
        completion(false)
    }
}

WKWebView Issues

Solutions:

import WebKit

class VerificationViewController: UIViewController, WKUIDelegate {
    var webView: WKWebView!

    override func viewDidLoad() {
        super.viewDidLoad()

        let config = WKWebViewConfiguration()
        config.allowsInlineMediaPlayback = true
        config.mediaTypesRequiringUserActionForPlayback = []

        webView = WKWebView(frame: view.bounds, configuration: config)
        webView.uiDelegate = self

        // Allow camera access
        if #available(iOS 14.0, *) {
            webView.configuration.defaultWebpagePreferences.allowsContentJavaScript = true
        }
    }
}

Android Issues

Permission Handling

Solutions:

  1. AndroidManifest.xml:
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-feature android:name="android.hardware.camera" android:required="true" />
  1. Runtime Permission Request:
class VerificationActivity : AppCompatActivity() {
    private val CAMERA_PERMISSION_CODE = 100

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        if (checkCameraPermission()) {
            startVerification()
        } else {
            requestCameraPermission()
        }
    }

    private fun checkCameraPermission(): Boolean {
        return ContextCompat.checkSelfPermission(
            this, Manifest.permission.CAMERA
        ) == PackageManager.PERMISSION_GRANTED
    }

    private fun requestCameraPermission() {
        ActivityCompat.requestPermissions(
            this,
            arrayOf(Manifest.permission.CAMERA),
            CAMERA_PERMISSION_CODE
        )
    }

    override fun onRequestPermissionsResult(
        requestCode: Int,
        permissions: Array<out String>,
        grantResults: IntArray
    ) {
        if (requestCode == CAMERA_PERMISSION_CODE) {
            if (grantResults.isNotEmpty() && 
                grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                startVerification()
            } else {
                showPermissionDeniedMessage()
            }
        }
    }
}

WebView Camera Access

Solutions:

class VerificationWebView : WebView {
    init {
        settings.apply {
            javaScriptEnabled = true
            mediaPlaybackRequiresUserGesture = false
            domStorageEnabled = true
        }

        webChromeClient = object : WebChromeClient() {
            override fun onPermissionRequest(request: PermissionRequest) {
                request.grant(request.resources)
            }
        }
    }
}

React Native Issues

Camera Module Not Found

Solutions:

// Install required packages
// npm install react-native-camera react-native-permissions

import { Camera } from 'react-native-camera';
import { check, request, PERMISSIONS, RESULTS } from 'react-native-permissions';

async function checkAndRequestCamera() {
  const permission = Platform.OS === 'ios' 
    ? PERMISSIONS.IOS.CAMERA 
    : PERMISSIONS.ANDROID.CAMERA;

  const result = await check(permission);

  if (result === RESULTS.DENIED) {
    const requestResult = await request(permission);
    return requestResult === RESULTS.GRANTED;
  }

  return result === RESULTS.GRANTED;
}

Authentication & API Keys

API Key Scope Errors

Symptoms:

{
  "error": "API key does not have permission for this product",
  "required_scope": "verifyage",
  "key_scopes": ["verifyhuman"]
}

Solutions:

  1. Create Product-Specific Keys:

    • Dashboard → API Keys → Create New Key
    • Select required scopes during creation
  2. Update Existing Key Scopes:

    • Dashboard → API Keys → [Your Key] → Scopes
    • Enable required products
  3. Unified Key System: All API keys now use the unified format: vhk-{32chars}

    Product Required Scope Example Key
    VerifyHuman verifyhuman vhk-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
    VerifyAge verifyage vhk-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
    VerifyIdentity verifyidentity vhk-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
    VerifyTrust verifycompliance vhk-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6

    A single key can have multiple scopes for accessing different products.

Two-Factor Authentication Issues

Symptoms:

Solutions:

  1. TOTP App Not Synced:

    • Check device time is accurate
    • Resync authenticator app
    • Use time-based sync in app settings
  2. Lost 2FA Access:

    • Use backup codes (generated during 2FA setup)
    • Contact support@verifyhuman.io with account verification
  3. Email OTP Not Received:

    • Check spam/junk folders
    • Whitelist noreply@verifyhuman.io
    • Wait 60 seconds for retry

Token Validation Issues

JWT Token Expired

Symptoms:

{
  "valid": false,
  "error": "Token expired"
}

Token Lifetime: 24 hours from creation

Solutions:

  1. Check Token Age:
import jwt
import time

def is_token_valid(token):
    try:
        # Decode without verification to check expiry
        payload = jwt.decode(token, options={"verify_signature": False})
        exp = payload.get('exp', 0)
        return time.time() < exp
    except jwt.exceptions.DecodeError:
        return False
  1. Request New Verification:
async function getValidToken(userId) {
  const storedToken = await getStoredToken(userId);

  if (storedToken && isTokenValid(storedToken)) {
    return storedToken;
  }

  // Token expired or invalid, require new verification
  return await requestNewVerification(userId);
}

Token Validation Failing

Symptoms:

{
  "valid": false,
  "error": "Invalid token signature"
}

Solutions:

  1. Always Validate on Backend:
def validate_token(token, client_secret):
    """Server-side token validation"""
    response = requests.post(
        'https://app.verifyhuman.io/api/validate',
        headers={
            'Authorization': f'Bearer {client_secret}',
            'Content-Type': 'application/json'
        },
        json={'token': token}
    )

    result = response.json()

    if not result.get('valid'):
        raise ValueError(f"Token validation failed: {result.get('error')}")

    return result
  1. Never Trust Client-Side Validation:
// WRONG - trusting client-side result
if (clientResult.token) {
  allowAccess();
}

// CORRECT - validate on backend
const backendResult = await fetch('/api/validate-verification', {
  method: 'POST',
  body: JSON.stringify({ token: clientResult.token })
});
if (backendResult.valid) {
  allowAccess();
}

Credit & Billing Issues

Unexpected Credit Deduction

Symptoms:

Solutions:

  1. Check for Retry Loops:
// BAD - infinite retry loop
async function verify() {
  try {
    await verifyHuman();
  } catch (error) {
    verify(); // Endless loop consuming credits!
  }
}

// GOOD - limited retries
async function verify(attempt = 0, maxAttempts = 3) {
  try {
    return await verifyHuman();
  } catch (error) {
    if (attempt < maxAttempts && error.retryable) {
      await sleep(1000 * Math.pow(2, attempt));
      return verify(attempt + 1, maxAttempts);
    }
    throw error;
  }
}
  1. Implement Verification Caching:
import hashlib
from datetime import datetime, timedelta

# Simple in-memory cache (use Redis in production)
verification_cache = {}

def cached_verify(user_id, image_data, cache_duration_hours=1):
    """Cache verification results to avoid duplicate calls"""
    cache_key = f"{user_id}:{hashlib.md5(image_data).hexdigest()}"

    if cache_key in verification_cache:
        cached = verification_cache[cache_key]
        if datetime.now() < cached['expires']:
            return cached['result']

    # Not in cache, perform verification
    result = perform_verification(image_data)

    verification_cache[cache_key] = {
        'result': result,
        'expires': datetime.now() + timedelta(hours=cache_duration_hours)
    }

    return result
  1. Monitor Credit Usage:
def verify_with_credit_check(api_key, product):
    credits_needed = {
        'verifyhuman': 1,
        'verifyage': 10,
        'verifyidentity': 30,
        'verifycompliance': 50
    }

    # Check credits before verification
    credits = get_credit_balance(api_key)
    needed = credits_needed[product]

    if credits < needed:
        send_low_credit_alert(credits, needed)
        raise InsufficientCreditsError(credits, needed)

    result = perform_verification()

    # Log credit usage
    log_credit_usage(api_key, product, needed)

    return result

Plan Access Restrictions

Symptoms:

{
  "error": "VerifyTrust requires Pro or Enterprise plan",
  "current_plan": "Growth"
}

Plan Capabilities:

Product Free Starter Growth Pro Enterprise
VerifyHuman
VerifyAge
VerifyIdentity
VerifyTrust

Solutions:


Performance Issues

Slow Verification Times

Symptoms:

Expected Performance:

Metric Target Maximum
Average Response Time < 3 seconds 10 seconds
P95 Response Time < 5 seconds 15 seconds
Timeout Threshold - 30 seconds

Solutions:

  1. Optimize Image Before Upload:
async function optimizeForUpload(file) {
  const canvas = document.createElement('canvas');
  const ctx = canvas.getContext('2d');
  const img = await createImageBitmap(file);

  // Target dimensions
  const maxWidth = 1920;
  const maxHeight = 1080;

  let { width, height } = img;

  if (width > maxWidth || height > maxHeight) {
    const ratio = Math.min(maxWidth / width, maxHeight / height);
    width *= ratio;
    height *= ratio;
  }

  canvas.width = width;
  canvas.height = height;
  ctx.drawImage(img, 0, 0, width, height);

  return new Promise(resolve => {
    canvas.toBlob(resolve, 'image/jpeg', 0.85);
  });
}
  1. Use Appropriate Timeout Settings:
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry

def create_session_with_retry():
    session = requests.Session()

    retry_strategy = Retry(
        total=3,
        backoff_factor=1,
        status_forcelist=[429, 500, 502, 503, 504]
    )

    adapter = HTTPAdapter(max_retries=retry_strategy)
    session.mount("https://", adapter)

    return session

session = create_session_with_retry()
response = session.post(
    'https://app.verifyhuman.io/api/verify',
    files={'file': image_file},
    data={'clientKey': api_key},
    timeout=(5, 30)  # (connect, read) timeouts
)
  1. Implement Progress Feedback:
async function verifyWithProgress(file, onProgress) {
  onProgress({ stage: 'uploading', percent: 0 });

  const formData = new FormData();
  formData.append('file', file);
  formData.append('clientKey', apiKey);

  const response = await fetch('/api/verify', {
    method: 'POST',
    body: formData
  });

  onProgress({ stage: 'processing', percent: 50 });

  const result = await response.json();

  onProgress({ stage: 'complete', percent: 100 });

  return result;
}

Widget Loading Slowly

Solutions:

  1. Lazy Load Widget:
function loadVerifyHumanWhenNeeded() {
  const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        loadWidget();
        observer.disconnect();
      }
    });
  });

  observer.observe(document.getElementById('verifyhuman-widget'));
}

function loadWidget() {
  const script = document.createElement('script');
  script.src = 'https://app.verifyhuman.io/widget.js';
  script.onload = () => {
    VerifyHuman.init({ apiKey: 'vhk-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6' });
  };
  document.head.appendChild(script);
}
  1. Preload Widget Script:
<link rel="preload" href="https://app.verifyhuman.io/widget.js" as="script">
  1. Use Deferred Loading:
<script src="https://app.verifyhuman.io/widget.js" defer></script>

Security Issues

Suspected Fraudulent Verifications

Indicators:

Solutions:

  1. Implement Additional Checks:
from collections import defaultdict
import time

# Track attempts by IP
ip_attempts = defaultdict(list)

def check_fraud_indicators(ip_address, result):
    current_time = time.time()

    # Clean old entries (last 5 minutes)
    ip_attempts[ip_address] = [
        t for t in ip_attempts[ip_address] 
        if current_time - t < 300
    ]

    # Record this attempt
    ip_attempts[ip_address].append(current_time)

    # Check for suspicious patterns
    if len(ip_attempts[ip_address]) > 10:
        flag_for_review(ip_address, "High velocity attempts")
        return True

    if result.get('confidence', 100) < 50:
        flag_for_review(ip_address, "Low confidence score")
        return True

    return False
  1. Use Device Fingerprinting:
VerifyHuman.init({
  apiKey: 'vhk-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6',
  collectDeviceInfo: true,
  onSuccess: (result) => {
    // Device info included in result
    console.log(result.device_fingerprint);
  }
});

API Key Compromised

Immediate Actions:

  1. Revoke the Compromised Key:

    • Dashboard → API Keys → [Compromised Key] → Revoke
  2. Generate New Key:

    • Dashboard → API Keys → Create New Key
    • Update all applications with new key
  3. Check for Unauthorized Usage:

    • Dashboard → Usage Logs
    • Look for unusual patterns
  4. Implement Key Rotation:

import os
from datetime import datetime, timedelta

def get_api_key():
    """Get current API key with automatic rotation awareness"""
    primary_key = os.environ.get('VERIFYHUMAN_API_KEY')
    backup_key = os.environ.get('VERIFYHUMAN_BACKUP_KEY')

    # Try primary first
    if verify_key_valid(primary_key):
        return primary_key

    # Fallback to backup
    if verify_key_valid(backup_key):
        alert_key_rotation_needed()
        return backup_key

    raise ConfigurationError("No valid API keys available")

Environment & Configuration

Development vs Production

Environment Indicators:

Setting Development Production
API Key Prefix _test_ _live_
Base URL Same Same
Credit Consumption No Yes
Verification Storage Temporary Persistent

Configuration Example:

import os

class Config:
    ENV = os.environ.get('FLASK_ENV', 'development')

    if ENV == 'production':
        VERIFYHUMAN_API_KEY = os.environ.get('VERIFYHUMAN_LIVE_KEY')
        VERIFYHUMAN_SECRET = os.environ.get('VERIFYHUMAN_LIVE_SECRET')
    else:
        VERIFYHUMAN_API_KEY = os.environ.get('VERIFYHUMAN_TEST_KEY')
        VERIFYHUMAN_SECRET = os.environ.get('VERIFYHUMAN_TEST_SECRET')

Environment Variables Missing

Required Variables:

# API Keys (use test keys for development)
VERIFYHUMAN_API_KEY=vhk-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6
VERIFYHUMAN_SECRET=vhs_xxx

# Optional
VERIFYHUMAN_WEBHOOK_SECRET=whsec_xxx
VERIFYHUMAN_LOG_LEVEL=INFO

Validation Script:

def validate_config():
    """Validate required environment variables"""
    required = [
        'VERIFYHUMAN_API_KEY',
        'VERIFYHUMAN_SECRET'
    ]

    missing = [var for var in required if not os.environ.get(var)]

    if missing:
        raise ConfigurationError(
            f"Missing required environment variables: {', '.join(missing)}"
        )

    # Validate key format (unified format: vhk-{32chars})
    api_key = os.environ.get('VERIFYHUMAN_API_KEY')

    if not api_key.startswith('vhk-') or len(api_key) != 36:
        raise ConfigurationError(
            "Invalid API key format. Expected format: vhk-{32chars}"
        )

    return True

Testing & Debugging

Test Mode

Using Test Keys:

Test Images:

# Sample test scenarios
TEST_SCENARIOS = {
    'pass_high_confidence': 'test_face_clear.jpg',
    'pass_low_confidence': 'test_face_dim.jpg',
    'fail_no_face': 'test_no_face.jpg',
    'fail_multiple_faces': 'test_multiple.jpg',
    'fail_liveness': 'test_photo_of_photo.jpg'
}

Debug Mode

Enable Verbose Logging:

VerifyHuman.init({
  apiKey: 'vhk-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6',
  debug: true,
  onDebug: (log) => {
    console.log('[VerifyHuman]', log.timestamp, log.level, log.message);
  }
});

Backend Debug Logging:

import logging

logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger('verifyhuman')

def verify_with_logging(image_data, api_key):
    logger.debug(f"Starting verification, image size: {len(image_data)} bytes")

    try:
        result = perform_verification(image_data, api_key)
        logger.debug(f"Verification result: {result}")
        return result
    except Exception as e:
        logger.error(f"Verification failed: {e}", exc_info=True)
        raise

Common Test Scenarios

import pytest

class TestVerification:
    def test_valid_face_passes(self, client, valid_face_image):
        result = client.verify(valid_face_image)
        assert result['status'] == 'PASS'
        assert result['confidence'] >= 70

    def test_no_face_fails(self, client, no_face_image):
        result = client.verify(no_face_image)
        assert result['status'] == 'FAIL'
        assert 'No face detected' in result['error']

    def test_multiple_faces_fails(self, client, multiple_faces_image):
        result = client.verify(multiple_faces_image)
        assert result['status'] == 'FAIL'
        assert 'Multiple faces' in result['error']

    def test_invalid_api_key_rejected(self, invalid_client, valid_face_image):
        with pytest.raises(AuthenticationError):
            invalid_client.verify(valid_face_image)

    def test_rate_limiting(self, client, valid_face_image):
        # Make many requests quickly
        for _ in range(100):
            try:
                client.verify(valid_face_image)
            except RateLimitError:
                return  # Expected

        pytest.fail("Rate limiting not triggered")

Error Code Reference

Complete Error Code Table

Code Error Description Solution
E001 Invalid API Key API key format incorrect or key doesn't exist Check key prefix and dashboard status
E002 Insufficient Credits Account has fewer credits than required Add credits or upgrade plan
E003 Rate Limit Exceeded Too many requests in time window Implement backoff, upgrade plan
E004 File Too Large File exceeds 10MB limit Compress image before upload
E005 No Face Detected AI couldn't find a face in image Improve lighting, positioning
E006 Multiple Faces More than one face detected Ensure single person in frame
E007 Liveness Failed Possible photo of photo detected Use live camera capture
E008 Token Invalid JWT token invalid or expired Validate on backend, check expiry
E009 Domain Not Whitelisted Request from unauthorized domain Add domain in dashboard
E010 Scope Denied API key lacks required scope Update key scopes in dashboard
E011 Plan Restricted Feature not available on plan Upgrade subscription plan
E012 Invalid File Format Unsupported image format Use JPG, PNG, or WebP
E013 File Corrupted Unable to process image file Re-capture or re-encode image
E014 Timeout Processing took too long Reduce image size, retry
E015 Service Unavailable API temporarily unavailable Check status page, retry later
E016 ID Not Recognized Cannot extract data from ID Improve ID photo quality
E017 Face Mismatch Selfie doesn't match ID photo Retake with similar conditions
E018 ID Expired Document past expiration date Use valid, non-expired ID
E019 Age Requirement Not Met User doesn't meet minimum age N/A - User rejected
E020 Compliance Hit AML/PEP/Sanctions match found Review compliance report
E021 Throttled Suspicious activity detected Wait for throttle period
E022 Maintenance System under maintenance Check status page

HTTP Status Code Reference

Status Meaning Action
200 Success Process response
400 Bad Request Check request format
401 Unauthorized Verify API key
403 Forbidden Check scopes/domain
404 Not Found Verify endpoint URL
413 Payload Too Large Reduce file size
429 Rate Limited Implement backoff
500 Server Error Retry, contact support
502 Bad Gateway Retry later
503 Service Unavailable Check status page

Getting Additional Help

Support Channels

Channel Availability Best For
Email 24/7 Detailed technical issues
Live Chat 9 AM - 5 PM EST Quick questions
Documentation 24/7 Self-service
Status Page 24/7 Service status

Contact Information:

When Contacting Support

Include This Information:

  1. Account Details:

    • Account email
    • API key prefix (e.g., vhk-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6...)
    • Current plan
  2. Issue Details:

    • Verification ID (if applicable)
    • Timestamp of issue
    • Error messages (full text)
    • HTTP status codes
  3. Technical Context:

    • Integration method (API, Widget, Hosted Flow)
    • Programming language/framework
    • Browser/OS (for widget issues)
  4. Reproduction Steps:

    • Minimal code sample
    • Sample image (if applicable)
    • Expected vs actual behavior

Example Support Request:

Subject: Liveness check failing for valid users

Account: user@company.com
API Key: vhk-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6... (first 15 chars)
Plan: Growth

Issue: ~20% of legitimate users are getting liveness failures

Verification IDs (recent failures):
- ver_abc123 (2025-11-25 10:30 UTC)
- ver_def456 (2025-11-25 10:45 UTC)

Integration: JavaScript Widget v2.1.0
Browser: Chrome 119 (mostly), also Safari 17

Steps:
1. User loads verification page
2. Widget initializes successfully
3. User takes selfie with front-facing camera
4. Verification returns FAIL with liveness_score: 35

Expected: These are real users with live camera captures
Actual: Getting liveness failures

No changes to our integration recently. Issue started ~2 days ago.

Last Updated: November 25, 2025
Version: 2.0

Related Documentation: