React Example
A complete React component that embeds the AgeEvidence verification widget, handles postMessage events, and includes a backend polling pattern.
VerificationWidget Component
This component renders the AgeEvidence iframe and exposes callback props for each verification lifecycle event. Replace pk_verify_YOUR_KEY with your publishable API key from the dashboard.
import { useState, useEffect, useCallback } from 'react';
const AGEEVIDENCE_URL = 'https://ageevidence.com';
const API_KEY = 'pk_verify_YOUR_KEY'; // Your publishable key
interface VerificationResult {
verificationId: string;
status: 'pending' | 'approved' | 'rejected';
}
function VerificationWidget({
externalId,
level = 'full_age',
onComplete,
onError,
onCancel,
}: {
externalId: string;
level?: 'age_only' | 'full_age' | 'full_kyc';
onComplete: (result: VerificationResult) => void;
onError: (error: string) => void;
onCancel?: () => void;
}) {
const [loaded, setLoaded] = useState(false);
const handleMessage = useCallback((event: MessageEvent) => {
if (event.origin !== AGEEVIDENCE_URL) return;
const { type, data } = event.data;
switch (type) {
case 'ae:ready':
setLoaded(true);
break;
case 'ae:complete':
onComplete({
verificationId: data.verificationId,
status: data.status,
});
break;
case 'ae:error':
onError(data.error);
break;
case 'ae:cancel':
onCancel?.();
break;
}
}, [onComplete, onError, onCancel]);
useEffect(() => {
window.addEventListener('message', handleMessage);
return () => window.removeEventListener('message', handleMessage);
}, [handleMessage]);
const params = new URLSearchParams({
apiKey: API_KEY,
externalId,
theme: 'dark',
locale: 'en',
parentOrigin: window.location.origin,
});
return (
<div style={{ position: 'relative' }}>
{!loaded && (
<div style={{
position: 'absolute',
inset: 0,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
background: '#111',
borderRadius: 12,
}}>
Loading...
</div>
)}
<iframe
src={`${AGEEVIDENCE_URL}/embed/${level}?${params}`}
width="100%"
height="600px"
frameBorder="0"
allow="camera"
style={{
border: 'none',
borderRadius: 12,
opacity: loaded ? 1 : 0,
}}
/>
</div>
);
}
// Usage
function App() {
const handleComplete = async (result: VerificationResult) => {
console.log('Verification submitted:', result.verificationId);
// Poll status from YOUR backend (which uses the secret key)
const status = await checkVerificationStatus('user_123');
if (status.verified) {
// Grant access
}
};
const handleError = (error: string) => {
console.error('Verification error:', error);
};
return (
<VerificationWidget
externalId="user_123"
level="full_age"
onComplete={handleComplete}
onError={handleError}
/>
);
}
// Call YOUR backend proxy (never call AgeEvidence API directly from browser)
async function checkVerificationStatus(externalId: string) {
const res = await fetch(`/api/check-verification?externalId=${externalId}`);
return res.json();
}Backend Polling
The checkVerificationStatus function calls your own backend, which in turn calls the AgeEvidence API with the secret key. Never expose sk_verify_ in browser code.
See the Status Polling guide for recommended polling cadence and backoff strategies.
Notes
- The
allow="camera"attribute on the iframe is required for camera access. - Always validate
event.originin the postMessage handler. - The widget is responsive and adapts to the container width.
- Use the
levelprop to switch betweenage_only,full_age, andfull_kyc.