Widget Integration Guide
Last Updated: November 30, 2025
Integration Method: JavaScript Widget (Embeddable)
Add VerifyHuman verification to any website with our embeddable JavaScript widgets. No backend required - just copy, paste, and go live.
📋 Table of Contents
- Overview
- Widget Types
- Quick Start
- Configuration Options
- Callbacks & Events
- Styling & Customization
- Advanced Integration
- Troubleshooting
Overview
VerifyHuman offers embeddable widgets for all four verification products:
| Widget | Purpose | Best For |
|---|---|---|
| VerifyHuman Widget | Liveness & bot detection | Signup forms, comment sections |
| VerifyAge Widget | Age verification | Age-restricted content, e-commerce |
| VerifyIdentity Widget | KYC verification | Account verification, marketplaces |
| VerifyTrust Widget | KYC+ with compliance | Financial apps, crypto exchanges |
Benefits:
- ✅ No backend integration required
- ✅ Camera access & image capture built-in
- ✅ Mobile-responsive design
- ✅ Customizable branding
- ✅ Real-time verification feedback
- ✅ Secure credit consumption
Widget Types
1. VerifyHuman Widget
Basic liveness detection and bot prevention widget.
<script src="https://app.verifyhuman.io/widget.js"></script>
<div id="verifyhuman-widget"></div>
<script>
VerifyHuman.init({
apiKey: 'vhk-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6',
onSuccess: function(result) {
console.log('Verified!', result);
},
onFailure: function(error) {
console.error('Failed:', error);
}
});
</script>
2. VerifyAge Widget
Age verification with two-step flow (selfie → ID if needed).
<script src="https://app.verifyhuman.io/widget-age.js"></script>
<div id="verifyage-widget"></div>
<script>
VerifyAge.init({
apiKey: 'vhk-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6',
useCase: 'alcohol',
minAge: 21,
onSuccess: function(result) {
console.log('Age verified!', result);
},
onFailure: function(error) {
console.error('Age verification failed:', error);
}
});
</script>
3. VerifyIdentity Widget
KYC widget with selfie + ID capture.
<script src="https://app.verifyhuman.io/widget-identity.js"></script>
<div id="verifyidentity-widget"></div>
<script>
VerifyIdentity.init({
apiKey: 'vhk-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6',
requireIdBack: false, // Optional ID back image
onSuccess: function(result) {
console.log('Identity verified!', result);
},
onFailure: function(error) {
console.error('Verification failed:', error);
}
});
</script>
4. VerifyTrust Widget
KYC+ widget with integrated compliance screening.
<script src="https://app.verifyhuman.io/widget-compliance.js"></script>
<div id="verifycompliance-widget"></div>
<script>
VerifyCompliance.init({
apiKey: 'vhk-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6',
requireIdBack: false,
onSuccess: function(result) {
if (result.compliance_screening.total_hits === 0) {
console.log('Cleared!', result);
} else {
console.warn('Compliance hits detected:', result.compliance_screening);
}
},
onFailure: function(error) {
console.error('Verification failed:', error);
}
});
</script>
Quick Start
Step 1: Include Widget Script
Add the widget script to your HTML <head> or before the closing </body> tag:
<!-- For VerifyHuman -->
<script src="https://app.verifyhuman.io/widget.js"></script>
<!-- For VerifyAge -->
<script src="https://app.verifyhuman.io/widget-age.js"></script>
<!-- For VerifyIdentity -->
<script src="https://app.verifyhuman.io/widget-identity.js"></script>
<!-- For VerifyTrust -->
<script src="https://app.verifyhuman.io/widget-compliance.js"></script>
Step 2: Add Widget Container
Place the widget container where you want verification to appear:
<div id="verifyhuman-widget"></div>
Step 3: Initialize Widget
Configure and initialize the widget:
<script>
VerifyHuman.init({
apiKey: 'vhk-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6',
buttonText: 'Verify You\'re Human',
onSuccess: function(result) {
// User verified successfully
document.getElementById('signup-button').disabled = false;
},
onFailure: function(error) {
// Verification failed
alert('Verification failed: ' + error.message);
}
});
</script>
Configuration Options
VerifyHuman Widget Options
VerifyHuman.init({
// Required
apiKey: 'vhk-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6',
// Optional
containerId: 'verifyhuman-widget', // Default: 'verifyhuman-widget'
buttonText: 'Verify You\'re Human', // Button label
buttonStyle: 'primary', // 'primary' | 'secondary' | 'minimal'
autoOpen: false, // Auto-open camera on page load
showConfidence: false, // Show confidence score to user
language: 'en', // 'en' | 'es' | 'fr' | 'de'
hostedUrl: null, // Your Hosted Verification Page URL — adds "Continue on mobile"
// Callbacks
onSuccess: function(result) { },
onFailure: function(error) { },
onClose: function() { },
onReady: function() { }
});
Continue on mobile (optional)
The VerifyHuman widget runs in one of two modes.
A. Standard on-device widget — the default. Verification happens in the visitor's current browser.
VerifyHuman.init({
clientKey: 'vhk_...',
apiBaseUrl: 'https://app.verifyhuman.io',
onSuccess: function (token) { /* send token to your backend */ },
onFailure: function (error) { }
});
B. Widget with mobile continuation — the same widget plus hostedUrl, the
URL of a Hosted Verification Page. The dashboard adds it
for you (see below).
VerifyHuman.init({
clientKey: 'vhk_...',
apiBaseUrl: 'https://app.verifyhuman.io',
hostedUrl: 'https://app.verifyhuman.io/hosted/v/<page-token>',
onSuccess: function (token) { /* send token to your backend */ },
onFailure: function (error) { }
});
The widget then offers two choices:
- Continue on this device — the normal in-page verification, unchanged.
onSuccess/onFailurefire exactly as in mode A. - Continue on mobile — the widget stays open on your page and shows a QR
code and a copyable mobile link. The visitor opens it on their phone and
finishes there; the widget shows "Verification in progress on your phone"
and then its normal result screen. Your page never navigates away.
onSuccess/onFailurereceive exactly what they receive in mode A: the VerifyHuman widget passes the same verification token (validate it on your server withPOST /api/validate, as always), and the VerifyAge widget passes the same result object. Your integration needs no second code path for visitors who continued on a phone.
You don't need to write mode B by hand. The VerifyHuman and VerifyAge Wizards and the Code Snippets page include mobile continuation automatically when they generate widget code: they prepare a hosted page for it behind the scenes, so there is nothing to create, select or paste. If that page can't be prepared at the moment, the page says so and shows the standard on-device widget instead. A published VerifyFlow VerifyHuman widget flow includes it automatically too, on the flow's own hosted page. A VerifyFlow VerifyAge widget flow keeps it as an opt-in switch for now: its widget and its hosted page do not yet apply the same age policy, so turning it on automatically could check a phone visitor differently from a visitor on this device.
How it works:
hostedUrlis a persistent hosted-page URL. It does not expire and is not tied to a visitor.- Only choosing Continue on mobile starts anything: the widget then creates that visitor's own hosted verification attempt. The desktop widget and the phone share that one attempt, so it is billed, receipted and reported once. Opening the widget, or continuing on this device, creates no attempt.
- The QR code and the mobile link carry only the hosted page and that attempt — the same link the hosted page's own "Continue on your phone" card uses. No new secret reaches the browser: your client key is never added to a URL and nothing identifies the visitor.
- The QR stays available in a narrow desktop window (for example with browser developer tools open); it scales down instead of disappearing.
The URL must be a /hosted/v/ page on the same VerifyHuman origin as
apiBaseUrl, over HTTPS; anything else is ignored and the widget behaves
exactly as it does without hostedUrl. Integrations that don't set
hostedUrl are unchanged.
Mobile continuation works the same way in the VerifyAge widget
(verifyage.js): pass hostedUrl to init() and the age step also offers
Continue on mobile. The Wizard's hosted page for it uses the same minimum
age and use case as the generated code, so the phone checks exactly what the
widget would.
VerifyAge Widget Options
VerifyAge.init({
// Required
apiKey: 'vhk-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6',
useCase: 'alcohol', // 'adult' | 'alcohol' | 'tobacco' | 'cannabis' | 'gambling' | 'custom'
minAge: 21,
// Optional
containerId: 'verifyage-widget',
buttonText: 'Verify Your Age',
buttonStyle: 'primary',
stateCode: 'CA', // For US state-specific compliance
multiStep: true, // Enable multi-step flow (DOB → Selfie → ID)
allowIdSkip: false, // Allow users to skip ID verification
showAgeEstimate: false, // Show estimated age to user
language: 'en',
// Callbacks
onSuccess: function(result) { },
onFailure: function(error) { },
onStepComplete: function(step, data) { },
onClose: function() { }
});
VerifyIdentity Widget Options
VerifyIdentity.init({
// Required
apiKey: 'vhk-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6',
// Optional
containerId: 'verifyidentity-widget',
buttonText: 'Verify Your Identity',
buttonStyle: 'primary',
requireIdBack: false, // Require ID back image
collectUserData: false, // Collect name, DOB, address fields
validateData: false, // Validate user input against OCR data
showExtractedData: false, // Show extracted data to user
language: 'en',
// Callbacks
onSuccess: function(result) { },
onFailure: function(error) { },
onDataExtracted: function(data) { },
onClose: function() { }
});
VerifyTrust Widget Options
VerifyCompliance.init({
// Required
apiKey: 'vhk-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6',
// Optional
containerId: 'verifycompliance-widget',
buttonText: 'Verify Identity & Compliance',
buttonStyle: 'primary',
requireIdBack: false,
collectGender: true, // Collect gender for better screening
autoGenerateReport: false, // Auto-generate PDF report if hits detected
reportPassword: 'secure123', // Password for encrypted PDF
language: 'en',
// Callbacks
onSuccess: function(result) { },
onFailure: function(error) { },
onComplianceHit: function(screening) { },
onReportGenerated: function(report) { },
onClose: function() { }
});
Callbacks & Events
onSuccess Callback
Called when verification succeeds.
onSuccess: function(result) {
console.log('Verification ID:', result.verification_id);
console.log('Status:', result.status);
console.log('Confidence:', result.confidence);
// For VerifyAge
if (result.verified) {
console.log('Age verified:', result.estimated_age);
}
// For VerifyIdentity/Compliance
if (result.document_data) {
console.log('Name:', result.document_data.name);
console.log('DOB:', result.document_data.date_of_birth);
}
// For VerifyTrust
if (result.compliance_screening) {
console.log('Compliance status:', result.compliance_screening.status);
console.log('Hits:', result.compliance_screening.total_hits);
}
}
onFailure Callback
Called when verification fails.
onFailure: function(error) {
console.error('Error code:', error.code);
console.error('Error message:', error.message);
// Handle specific errors
switch(error.code) {
case 'INSUFFICIENT_CREDITS':
alert('Out of credits. Please upgrade your plan.');
break;
case 'INVALID_API_KEY':
console.error('API key is invalid or inactive');
break;
case 'CAMERA_DENIED':
alert('Please allow camera access to verify.');
break;
case 'LOW_CONFIDENCE':
alert('Verification failed. Please try again with better lighting.');
break;
default:
alert('Verification failed: ' + error.message);
}
}
onStepComplete Callback (VerifyAge only)
Called when each step of multi-step verification completes.
onStepComplete: function(step, data) {
console.log('Completed step:', step); // 'dob' | 'selfie' | 'id'
if (step === 'dob' && data.underage) {
// User is underage, verification stopped
console.log('User is underage:', data.user_age);
}
if (step === 'selfie' && data.requires_id) {
// Selfie age estimation requires ID backup
console.log('ID verification required');
}
}
onComplianceHit Callback (VerifyTrust only)
Called when compliance screening detects hits.
onComplianceHit: function(screening) {
console.log('Compliance hits detected:', screening.total_hits);
console.log('Status:', screening.status); // 'Potential Match'
console.log('Found records:', screening.found_records);
// Flag user for manual review
flagForReview(screening);
}
Styling & Customization
Button Styles
Choose from pre-defined button styles:
// Primary (default) - Blue button
buttonStyle: 'primary'
// Secondary - Gray button
buttonStyle: 'secondary'
// Minimal - Text link style
buttonStyle: 'minimal'
// Custom - Use your own CSS
buttonStyle: 'custom'
Custom CSS
Override widget styles with custom CSS:
<style>
/* Customize button */
.verifyhuman-button {
background-color: #10b981;
color: white;
border-radius: 8px;
padding: 12px 24px;
font-size: 16px;
font-weight: 600;
}
.verifyhuman-button:hover {
background-color: #059669;
}
/* Customize modal */
.verifyhuman-modal {
border-radius: 12px;
box-shadow: 0 10px 40px rgba(0,0,0,0.2);
}
/* Customize video preview */
.verifyhuman-video {
border-radius: 8px;
border: 2px solid #10b981;
}
</style>
Dark Mode Support
Widgets automatically detect and adapt to dark mode:
VerifyHuman.init({
apiKey: 'vhk-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6',
theme: 'auto', // 'auto' | 'light' | 'dark'
});
Advanced Integration
Signup Form Integration
Require verification before form submission:
<form id="signup-form">
<input type="email" name="email" placeholder="Email" required>
<input type="password" name="password" placeholder="Password" required>
<div id="verifyhuman-widget"></div>
<button type="submit" id="signup-button" disabled>
Sign Up
</button>
</form>
<script>
VerifyHuman.init({
apiKey: 'vhk-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6',
onSuccess: function(result) {
// Enable submit button
document.getElementById('signup-button').disabled = false;
// Store verification token
document.getElementById('signup-form').addEventListener('submit', function(e) {
e.preventDefault();
// Send verification token with form data
fetch('/api/signup', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: document.querySelector('[name="email"]').value,
password: document.querySelector('[name="password"]').value,
verification_token: result.token
})
});
});
}
});
</script>
Comment Section Integration
Verify users before allowing comments:
<textarea id="comment-text" placeholder="Write your comment..." disabled></textarea>
<button id="comment-submit" disabled>Post Comment</button>
<div id="verifyhuman-widget"></div>
<script>
VerifyHuman.init({
apiKey: 'vhk-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6',
buttonText: 'Verify to Comment',
onSuccess: function(result) {
// Enable comment form
document.getElementById('comment-text').disabled = false;
document.getElementById('comment-submit').disabled = false;
// Store verification ID
sessionStorage.setItem('verified_user', result.verification_id);
}
});
</script>
Age-Gated Content
Show content only after age verification:
<div id="age-gate">
<h2>Age Verification Required</h2>
<p>You must be 21+ to access this content.</p>
<div id="verifyage-widget"></div>
</div>
<div id="restricted-content" style="display: none;">
<!-- Age-restricted content here -->
</div>
<script>
VerifyAge.init({
apiKey: 'vhk-a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6',
useCase: 'alcohol',
minAge: 21,
onSuccess: function(result) {
// Hide age gate
document.getElementById('age-gate').style.display = 'none';
// Show restricted content
document.getElementById('restricted-content').style.display = 'block';
// Store verification in session
sessionStorage.setItem('age_verified', 'true');
}
});
// Check if already verified
if (sessionStorage.getItem('age_verified')) {
document.getElementById('age-gate').style.display = 'none';
document.getElementById('restricted-content').style.display = 'block';
}
</script>
Troubleshooting
Widget Not Appearing
Problem: Widget container is empty.
Solutions:
- Check that script is loaded:
console.log(typeof VerifyHuman) - Verify container ID matches:
containerId: 'your-id' - Check browser console for errors
- Ensure API key is valid
Camera Not Working
Problem: Camera permission denied or not found.
Solutions:
- Check HTTPS: Widgets require HTTPS (except localhost)
- Allow camera permission in browser
- Test camera:
navigator.mediaDevices.getUserMedia({ video: true }) - Check for conflicting apps using camera
Verification Failing
Problem: All verifications return FAIL status.
Solutions:
- Check API key is active in dashboard
- Verify account email is confirmed
- Check credits remaining
- Test with good lighting and clear face
- Check browser console for error details
Styling Issues
Problem: Widget doesn't match site design.
Solutions:
- Use
buttonStyle: 'custom'for full control - Override CSS classes (see Custom CSS section)
- Use
themeoption to match light/dark mode - Contact support for custom branding (Enterprise)
Browser Support
Widgets support all modern browsers:
| Browser | Minimum Version |
|---|---|
| Chrome | 60+ |
| Firefox | 55+ |
| Safari | 11+ |
| Edge | 79+ |
| Opera | 47+ |
| Samsung Internet | 8+ |
Mobile Support:
- iOS Safari 11+
- Android Chrome 60+
- Android Firefox 55+
Requirements:
- JavaScript enabled
- Camera access (getUserMedia API)
- HTTPS (except localhost)
Security Considerations
API Key Exposure
Safe: Client-side API keys (vhk-{32chars}) can be exposed in frontend code.
Reason: Keys are scoped to specific products and have built-in rate limiting. They cannot access your account settings or other sensitive data.
Token Validation
Best Practice: Always validate verification tokens on your backend:
// Frontend: Send token to backend
onSuccess: function(result) {
fetch('/api/validate-verification', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token: result.token })
});
}
// Backend: Validate with VerifyHuman
const response = await fetch('https://app.verifyhuman.io/api/validate', {
method: 'POST',
headers: {
'Authorization': 'Bearer vhsk-your_api_secret_here',
'Content-Type': 'application/json'
},
body: JSON.stringify({ token: userToken })
});
CORS & Domain Restrictions
Configure allowed domains in your dashboard:
- Go to Dashboard → API Keys
- Select your API key
- Add Allowed Domains (e.g.,
yourdomain.com) - Save changes
Support
Need help with widget integration?
- Email: support@verifyhuman.io
- Live Chat: Available in your dashboard
- Documentation: docs.verifyhuman.io
- Examples: github.com/verifyhuman/examples