@maiguard-hq/verify
v0.2.0
Published
MaiGuard Verify Web SDK — facial verification, liveness detection, and identity capture
Maintainers
Readme
@maiguard-hq/verify
MaiGuard Verify Web SDK — facial verification, liveness detection, and identity capture for browser applications.
Installation
npm install @maiguard-hq/verifyOr via CDN:
<script src="https://cdn.maiguard.com/sdk/verify/v1/maiguard-verify.min.js"></script>Quick Start
import { MaiGuardVerify } from '@maiguard-hq/verify';
const verify = new MaiGuardVerify({
apiKey: 'pk_live_your_key',
containerId: '#verify-container',
// MaiGuard's web adapter runs the official provider capture UI and returns the opaque
// server-created liveness session id. No browser boolean can attest liveness.
providerLivenessHandler: runMaiGuardProviderLiveness,
});
// Listen to events
verify.on('liveness:challenge', (state) => {
console.log(`Challenge ${state.index + 1}/${state.total}: ${state.challenge}`);
});
verify.on('verification:complete', (result) => {
console.log('Verification:', result.status, result.confidenceScore);
});
// Run verification
const result = await verify.start({
customerId: 'user_123',
verificationPurpose: 'onboarding',
referenceSource: 'nin',
referenceImage: {
type: 'data_url',
dataUrl: trustedNinPortrait,
},
checks: ['liveness', 'face_match'],
consent: { granted: true, basis: 'explicit_consent' },
});
if (result.status === 'verified') {
// Identity confirmed
}
// Cleanup
verify.destroy();For a non-Nigerian journey, pass the subject's ISO-2 country to verify.start (for example, country: 'GH'). Explicit codes are normalized to uppercase and malformed or empty values are rejected before capture. Omitting country preserves the existing NG fallback for legacy integrations; it does not assert that a check is available in Nigeria or any other country. The MaiGuard API remains authoritative for tenant policy and provider coverage. A providerLivenessHandler receives the resolved country in its context. Headless API calls can also derive it from a declared subject jurisdiction.
Framework Integration
React
import { MaiGuardVerify } from '@maiguard-hq/verify';
import { useEffect, useRef } from 'react';
function VerifyComponent({ onComplete }) {
const containerRef = useRef(null);
useEffect(() => {
const verify = new MaiGuardVerify({
apiKey: 'pk_live_...',
containerId: '#verify-mount',
providerLivenessHandler: runMaiGuardProviderLiveness,
});
verify.start({
customerId: 'user_123',
verificationPurpose: 'onboarding',
referenceSource: 'bvn',
referenceImage: { type: 'data_url', dataUrl: trustedBvnPortrait },
checks: ['liveness', 'face_match'],
consent: { granted: true },
}).then(onComplete);
return () => verify.destroy();
}, []);
return <div id="verify-mount" ref={containerRef} />;
}Vanilla JavaScript
<div id="verify-container"></div>
<script src="https://cdn.maiguard.com/sdk/verify/v1/maiguard-verify.min.js"></script>
<script>
const verify = new MaiGuardVerify({
apiKey: 'pk_live_...',
containerId: '#verify-container',
providerLivenessHandler: runMaiGuardProviderLiveness,
});
verify.start({
customerId: '12345',
verificationPurpose: 'account_recovery',
referenceSource: 'internal_profile',
referenceImage: { type: 'secure_url', url: trustedPortraitUrl },
checks: ['liveness', 'face_match'],
consent: { granted: true },
}).then(result => {
console.log(result.status);
});
</script>Events
| Event | Payload | Description |
|-------|---------|-------------|
| camera:ready | { devices } | Camera stream started |
| face:detected | FaceDetectionResult | Face found in frame |
| face:lost | — | Face no longer visible |
| quality:check | FaceDetectionResult | Quality check run |
| liveness:start | { challenges } | Liveness flow began |
| liveness:challenge | LivenessChallengeState | Challenge state change |
| liveness:complete | LivenessResult | All challenges done |
| upload:progress | UploadProgress | Upload progress |
| verification:complete | VerificationResult | Final result |
| error | { error, code, recoverable } | Any error |
Security
- Long-lived provider credentials are never embedded in the SDK. A capture adapter may receive short-lived, least-privilege credentials from MaiGuard for the active session only.
- Face matching cannot run without liveness in the same SDK flow
- Liveness requires a provider-attested session; browser gesture results are never converted into a pass
- The trusted portrait may come from NIN, BVN, a document, or another partner-approved source
- Secure reference URLs are fetched in the browser with credentials omitted, redirects blocked, content type checked, and a 10 MB size limit
- The API receives image data, not an arbitrary URL to fetch server-side
- Biometric data is kept in memory only for the active verification flow
- HTTPS enforced
- Device metadata sent for fraud analysis
The web SDK uses a providerLivenessHandler boundary for the official provider capture component.
The handler returns an opaque session id; MaiGuard retrieves the provider result server-side and uses the
provider-selected live frame for face matching. MediaPipe positioning may guide capture, but it is never
authoritative liveness evidence.
Reference image model
MaiGuard Verify is reference-image agnostic. The integrating business chooses the trusted identity source, while the SDK owns consent, camera guidance, live capture, liveness, and submission to MaiGuard for face matching and decisioning.
Supported browser inputs are:
{ type: 'data_url', dataUrl }for a trusted base64 portrait, including NIN or BVN responses{ type: 'blob', blob }for a browserBloborFile{ type: 'secure_url', url }for a short-lived HTTPS URL that allows cross-origin browser access{ type: 'server_reference', referenceId }for a first-party flow whose customverificationHandlerresolves an existing protected portrait without sending it to the browser
Use verification:complete or the returned promise as the in-app result handler. Configure server-to-server webhooks in MaiGuard rather than accepting a callback URL from browser code.
Verify identity graph capture
When the merchant enables identity-link face search, supply a stable applicationRef for the application.
Forward the handler's applicationRef, country and metadata unchanged when creating the liveness
session; submit that same application reference and returned session ID for verification. Sessions are
scoped to the merchant, environment and application, expire after three minutes, and are consumed once.
A new capture attempt needs a new provider session. Retries of a consumed capture return its saved result.
Request consent.scopes: ['biometric', 'biometric_search', 'biometric_index'] only after the applicant
explicitly agrees to those purposes. Search and enrollment are separate permissions; ordinary biometric
consent does not authorize either. MaiGuard's tenant policy must enable each operation as well.
Device attributes are reported context. MaiGuard records the connection IP and, when available, country
inferred from its local IP database. This is Verify application context, separate from Sentinel telemetry.
A backend relay should send metadata.transport: 'merchant_relay'; its connection IP is the backend's IP.
No precise GPS is collected. Keep these fields out of merchant debug logs.
Read identityLinkAssessment independently of the verification checks. incomplete and not_checked
do not mean that no related identity exists. A face candidate never automatically merges identities.
