A fast, guided camera
A standalone browser SDK for a clear face photo or a short randomized live selfie. It coexists with the legacy camera and never replaces window.VerifyCamera.
Install
<script src="https://www.verifyfaceid.com/camera-v3.js?v=3.14.0"></script>
The SDK begins loading its pinned, self-hosted face detector in the background. Call preload() earlier if the script itself is loaded on demand.
await VerifyCameraV3.preload();
Capture a live selfie
try {
const selfie = await VerifyCameraV3.capture();
// Upload-ready JPEG Blob
formData.append('selfie', selfie.blob, 'selfie.jpg');
// Optional local preview
preview.src = selfie.image;
} catch (error) {
if (error.code !== 'cancelled') {
console.error(error.code, error.message);
}
}
The default liveness mode guides the person through a short on-screen challenge and then takes the photo automatically once the result is a settled, front-facing frame. The challenge is generated fresh for every attempt, so it cannot be rehearsed or prepared in advance. Clear guidance is shown throughout for framing, lighting, and each requested movement. A failed attempt can be retried up to five times without reopening the camera or repeating the permission prompt.
Designed for a quick phone flow
The full-screen controls respect phone safe areas, work in portrait and landscape, and keep a labeled Cancel action visible throughout. Retries are automatic: each gets a new challenge while the existing camera stream stays open, so a stumble never requires a tap.
Complete v3 flow
// A trusted reference needs a clear centered photo, not a live challenge.
const reference = await VerifyCameraV3.capture({ mode: 'photo' });
// The fresh selfie receives the randomized live challenge.
const selfie = await VerifyCameraV3.capture({ mode: 'liveness' });
const body = new FormData();
body.append('reference', reference.blob, 'reference.jpg');
body.append('selfie', selfie.blob, 'selfie.jpg');
// Send the Blobs to your own backend. Your backend forwards the multipart
// request to VerifyFaceID and adds its Bearer API key there.
const response = await fetch('/your-backend/verify-face', {
method: 'POST',
body
});
const result = await response.json();
Keep the API key on your server
VerifyFaceID API keys are long-lived credentials. Do not embed one in JavaScript shipped to a public browser. Let your backend add the Authorization: Bearer header when it forwards the two files to v3.
Capture result
{
image: "data:image/jpeg;base64,…",
blob: Blob,
mimeType: "image/jpeg",
width: 900,
height: 900,
size: 183204,
liveness: {
performed: true,
passed: true,
challenge: "turn_left",
durationMs: 1842
}
}
In photo mode, performed is false and the other liveness values are null. The image is cropped around the detected face, encoded as JPEG, and kept below the API’s upload limit.
For compatibility with earlier v3 integrations, liveness.challenge remains "turn_left" or "turn_right" and identifies the first on-screen prompt. The remainder of the challenge is intentionally not exposed in the result object.
Public methods
| Method | Returns | Purpose |
|---|---|---|
| capture(options?) | Promise<Capture> | Open the guided camera and return the structured result. |
| takePicture(options?) | Promise<string> | Convenience method returning only the JPEG data URL. |
| preload() | Promise<true> | Warm the SDK runtime and its detection assets before capture. |
| cancel() | void | Cancel the active capture and stop all media tracks. |
| isSupported() | boolean | Quick feature check. |
| getSupport() | object | Returns supported and a machine-readable reason. |
Options
| Option | Default | Notes |
|---|---|---|
| mode | "liveness" | liveness or the centered photo mode. |
| facingMode | "user" | user or environment; requested as an ideal constraint. |
| mirror | true for front camera | Mirrors the preview and direction calculation. The saved JPEG is not mirrored. |
| signal | null | An AbortSignal for application-driven cancellation. |
| challengeTimeoutMs | 20000 | Time allowed to complete the on-screen challenge, clamped from 6 to 30 seconds. Shorter values automatically receive a shorter challenge. |
| maxAttempts | 6 | One initial attempt plus five retries by default; total attempts are clamped from 1 to 10. Retries use a new challenge and the same camera stream. |
| permissionTimeoutMs | 30000 | How long an unanswered browser permission may remain open. |
| maxDimension | 900 | Square JPEG dimension, clamped from 480 to 1280. |
| jpegQuality | 0.90 | Clamped from 0.80 to 0.95 and reduced if needed for size. |
Stable error handling
All SDK failures reject with VerifyCameraV3.Error. Check error.code, not browser-specific error text.
try {
await VerifyCameraV3.capture();
} catch (error) {
switch (error.code) {
case 'cancelled':
case 'aborted':
break;
case 'permission_denied':
showCameraPermissionHelp();
break;
default:
showRetryMessage(error.message);
}
}
Common codes include camera_busy, insecure_context, camera_blocked, camera_unsupported, permission_denied, permission_timeout, camera_not_found, camera_in_use, model_load_failed, face_timeout, challenge_failed, challenge_timeout, camera_interrupted, and capture_failed. The boolean recoverable property helps separate unsupported environments from retryable failures.
Browser and permission requirements
- Serve the page over HTTPS. Browsers expose
getUserMedia()only in a secure context. - Request camera access in response to a user action, such as a button click.
- No microphone permission is requested.
- For an iframe, the top-level site must allow camera use and the iframe must include
allow="camera". - Use
getSupport()before presenting the capture button when supporting older or embedded browsers.
<iframe
src="https://your-app.example/verify"
allow="camera"
></iframe>
Content Security Policy
If your site uses a strict CSP, allow the hosted SDK and its runtime in script-src and allow its asset downloads from the same origin in connect-src. When the main SDK script has a nonce, v3 copies it to the script and style it injects; otherwise the injected style must be allowed by style-src.
What the live challenge does—and does not prove
The challenge begins only after the SDK has a clear, well-lit view of a single face, and it is generated fresh for each attempt using a cryptographic random source. Throughout the flow the SDK verifies that it is watching one continuous face and that each response is newly performed rather than a held pose or a stale reading, and the automatic capture waits for a settled, front-facing frame suitable for face comparison. The signals, thresholds, and sequencing behind the challenge are internal to the SDK and may change between versions without notice; do not build integrations that depend on them.
A randomized motion check—not certified liveness
The challenge raises the effort needed to pass with a static photo or a screen held in front of an ordinary camera, and it produces a fresh, well-framed selfie. Because it runs entirely in the visitor’s browser, it cannot prove that a live person was physically present, and a modified client can bypass it. Treat the liveness object as a usability and deterrence signal, never as a signed attestation, and use a server-verified liveness service when your threat model requires stronger assurance.
Coexisting with the legacy camera
<script src="https://www.verifyfaceid.com/camera.js?v=2.0.27"></script>
<script src="https://www.verifyfaceid.com/camera-v3.js?v=3.14.0"></script>
// Existing integration remains available:
await VerifyCamera.takePicture();
// New integration uses a different global:
await VerifyCameraV3.capture();
The globals can coexist during a gradual migration. If both scripts are needed on one page, load the legacy script first as shown and do not open both camera generations at the same time. New pages should load only v3 for the smallest, fastest path.