JavaScript SDK
The Verifa JavaScript SDK handles the frontend capture experience — opening the verification UI as a popup, modal, or redirect. Your backend creates the session, the SDK handles the rest.
Two API styles
Verifa exposes the same capabilities through two equivalent JavaScript APIs:
Both call the same underlying code path. Pick one and stay consistent. The rest
of this page uses the flat Verifa.open() form because it’s been around longer;
skip to the Verifa.Client reference for the new form.
Recommended integration
The most secure and production-ready approach: your backend creates the session using a secret API key, immediately exchanges it for a short-lived embed token, and passes ONLY that token’s URL to the frontend. The long-lived capture URL never leaves your server.
This matches the pattern used by Persona (inquiry session token), Stripe
Identity (client_secret), and Plaid Link (link_token).
1. Add the script
Or via npm:
Required host-page headers
The widget loads the capture flow inside an <iframe> on your page. For the
iframe to access the camera and render correctly, your page must permit it.
Most customers don’t set the relevant headers at all — in which case
browser defaults apply and everything works out of the box. If your page does
set Content-Security-Policy or Permissions-Policy, you must explicitly
allow Verifa’s origin.
Content-Security-Policy
Permissions-Policy
Delegate camera and microphone to Verifa’s origin (the iframe’s allow=
attribute alone is not sufficient — the host’s Permissions-Policy is the gate):
Symptom of misconfiguration
If your headers block the iframe, the modal opens but the camera permission
prompt never appears and the capture page stays blank. The SDK detects this
case and emits onError({ code: "ready_timeout", message: "..." }) after
8 seconds (configurable via readyTimeoutMs). Check your browser’s
developer console for messages mentioning “Content Security Policy”,
“Permissions-Policy”, or “frame-ancestors” — those will pinpoint the
exact header that needs to change.
2. Create a session and issue a client token on your server
Two API calls, both with the secret API key:
Python
Node
cURL
The embed_url and client_token both expire in 30 minutes. The long-lived
session capture URL (session.capture_url) lives 24 hours, but you don’t
need to expose it to the browser — that’s the whole point of this pattern.
If a user takes a long time to finish, re-issue the client token on your
backend before it expires.
3. Open the capture UI on your frontend
Equivalent using the Persona-shaped sessionToken form:
Both produce the same iframe URL — pick whichever fits your codebase.
4. Get the result
Results contain PII and must be retrieved server-side:
Or receive results via webhook (recommended for production).
API reference
Verifa.Client class API
Class-based API matching the Persona-style surface. Construct an instance with
your options, then call .open(). The instance manages lifecycle and emits
callbacks for each verification event.
Constructor options
Methods
Verifa.open(options) — flat / compat form
Equivalent to new Verifa.Client(...).open() but returns a Promise. Useful
for await style or existing integrations.
Returns: Promise<{ sessionId: string, status: string }>
Display modes
Popup (default)
Opens the capture UI in a centered popup window. Best for desktop.
If the browser blocks the popup, onError fires. Fall back to modal mode.
Modal
Opens the capture UI in a full-screen overlay with an iframe. Works on desktop and mobile. Closes with the X button, backdrop click, or Escape key.
Redirect
Navigates the browser to the capture page. After completion, the user returns
to the redirect_url you specified when creating the session.
The user lands back on your redirect URL with query parameters:
Framework examples
React
Next.js (app router)
The SDK only runs in the browser. Mark the component "use client" and load
the bundle with next/script so the integrity hash + crossorigin are set
correctly.
The session-create call (/api/create-verification) lives in a Next.js
Route Handler
or Server Action so the secret key never reaches the browser bundle.
Vue
Vanilla JavaScript
Error handling
onError is called with { code, message } for every recoverable and
unrecoverable failure. Branch on code for stable handling — message may
change between versions.
Error code reference
onError always fires once and then the SDK tears down — onComplete will not
fire after an error. If you need both branches, mirror the logic:
Sandbox testing
Use sandbox API keys to test without real verifications. In sandbox mode, the capture flow lets you choose the outcome (approve, reject, or needs review).
Edge cases
Mobile Safari before iOS 16.4
getUserMedia inside a cross-origin iframe was unreliable on iOS Safari
prior to 16.4 (March 2023). On older devices the modal opens but the
camera prompt never appears, and the SDK fires
onError({ code: 'ready_timeout' }) after the timeout. Detect this and
offer the user a fallback:
Popup blockers
mode: 'popup' requires .open() to be called from a synchronous click
handler. If you await a fetch before opening the popup, browsers will
block it. Two patterns work:
ready_timeout debugging
If the modal opens but the camera prompt never appears and you get
ready_timeout, your host page is almost certainly blocking the iframe.
Check the browser console:
If nothing shows in the console, the iframe loaded fine but the capture
JavaScript itself failed to boot. Inspect the iframe’s network tab for a
401/403 on /verify/* — that means the capture URL has expired or the
session was already submitted.
Hidden iframe / tab visibility
When the modal is open and the user switches tabs, the camera stream pauses (browser-level behavior). The capture flow resumes when the tab is focused again. No SDK action needed.
Strict CSP (Content-Security-Policy: ...; sandbox)
Pages with a strict sandbox directive can’t open child iframes at all. If
you must run with sandbox, use mode: 'redirect' — the user leaves your
page, completes verification on app.withverifa.com, and returns via
redirectUrl.
Browser support
The SDK uses fetch, Promise, and postMessage, supported in all modern
browsers:
- Chrome 42+, Firefox 39+, Safari 10.1+, Edge 14+
No polyfills required for any browser released after 2017.
Security
- No API key in the browser — With the recommended
captureUrlflow, the secret key stays on your server. The frontend only receives a capture URL scoped to one session, which expires when the session does (default 24 h, configurable per-session). - Origin allowlist on publishable keys — The optional quickstart mode uses a
publishable key (
vk_pub_*). Publishable keys are required to declare an Allowed Origins list; requests from any other origin are rejected with 403. See Authentication → Publishable keys. - Subresource Integrity — The CDN script tag carries an
integrity=hash so a tampered script can’t execute. The browser refuses the script if the hash doesn’t match the bytes received. - No PII exposure — The SDK never receives verification results, document images, or personal data. Results are fetched server-side or pushed via webhook.
- Token-based capture — Document uploads inside the capture iframe use a session-specific token, never an API key.
Related
- Web Capture Flow — Full server-side integration guide
- Choosing an Integration Method — Compare all integration options
- Authentication — API key types and scopes
- Webhooks Guide — Receive results in real-time
- Testing — Sandbox testing guide