Create
Your backend calls POST /api/v1/verifications with your API key and liveness_tier, exactly as in redirect mode, and hands the returned url to your page.
Soliton Identity Verification
The Web SDK runs the hosted verification flow in a full-screen overlay iframe instead of a redirect. Your backend integration (create, verdict, portrait) is identical to the server API guide; this page covers only what changes in the browser.
01
Same verification, same one-time url; the SDK replaces the redirect hand-off with callbacks.
Your backend calls POST /api/v1/verifications with your API key and liveness_tier, exactly as in redirect mode, and hands the returned url to your page.
Your page calls open({ url, ... }). The SDK covers the viewport with an overlay iframe pointing at soliton.id.
The user completes the camera challenge inside the iframe, on soliton.id. Your page never touches video or biometrics.
onComplete fires with the verification id only. Your backend fetches the verdict server-to-server, exactly as in redirect mode.
Set liveness_tier when creating the verification: standard uses the camera with no sound or flashes; medium adds sound; high adds sound and light. Omitting the field preserves High for existing integrations. Standard requests camera permission only; Medium and High also request microphone access.
// Your backend only. Use sk_test_... for sandbox or sk_live_... for production.
const livenessTier = "standard"; // "standard" | "medium" | "high"; your server's policy
const response = await fetch("https://api.soliton.id/api/v1/verifications", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.SOLITON_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
liveness_tier: livenessTier,
client_reference_id: "user-8412",
}),
});
if (!response.ok) throw new Error("Could not create verification");
const verification = await response.json();
// Store verification.verification_id, livenessTier and verification.environment
// against your authenticated user. Return only { url: verification.url } to the page.All three tiers work with sandbox and production keys. The returned URL binds the choice for every capture and retake. Pass that URL to open(); there is no tier option or tier query parameter to set in the SDK. Existing SDK versions support every tier. Create a new verification if you need a different tier.
After onComplete, fetch the stored verification id from your backend and require the result's liveness_tier and environment to match your stored choice. Standard always returns partial_verification: false; Medium and High can return partial outcomes. See the full tier and verdict contract.
02
Embedding is off by default.
Ask us to register the exact origin of every page that runs the SDK and directly contains its Soliton iframe (scheme + host + port, e.g. https://app.example.com) for your Soliton account. This direct-parent page does not have to be the browser's top-level page. Register its origin, not its path: https://app.example.com and https://app.example.com:8443 are different origins, while /verify is not part of an origin. A verification link opened inside an iframe with any other direct-parent origin refuses to start and reports origin_not_allowed. Non-iframe opens (redirect mode) are unaffected: the same link works standalone or embedded. Redirect-domain registration and iframe-origin registration are separate; enabling one does not enable the other.
Origins are registered per environment, so tell us which one each is for. A link is checked against the origins registered for the environment of the key that created it: your staging page can embed sk_test_ links without being able to embed sk_live_ ones.
CSP (Content Security Policy) is the browser response header that controls which sources a page may load. If the page that runs the SDK sends a restrictive CSP, merge the Soliton source into its existing directives; do not replace the rest of your policy with these minimal examples. The frame-src source is required for every SDK distribution. The script-src source is needed only when loading the classic or ESM bundle directly from soliton.id, not when your app bundles the npm package. If any document above the Soliton iframe restricts camera or microphone in Permissions-Policy, every level in a nested frame chain must allow and delegate camera access for Standard, and camera plus microphone access for Medium and High, through each intervening iframe; an upstream denial cannot be overridden lower in the chain. The SDK already adds the matching allow attribute on the iframe it creates.
# Production example for the npm package:
Content-Security-Policy: frame-src https://soliton.id
Permissions-Policy: camera=(self "https://soliton.id"), microphone=(self "https://soliton.id")# Production example for the classic CDN script or hosted ESM:
Content-Security-Policy: frame-src https://soliton.id; script-src https://soliton.id
Permissions-Policy: camera=(self "https://soliton.id"), microphone=(self "https://soliton.id")http://localhost:3000), for registration. It does not belong in frame-src or script-src while the iframe and hosted SDK still come from soliton.id.frame-ancestors. That directive controls who may frame your page; frame-src controls what your page may frame.03
No runtime dependencies, no framework requirements.
npm install @soliton-id/web-sdk<script src="https://soliton.id/sdk/v1/soliton-web-sdk.js"></script>
<!-- exposes the global SolitonVerify; SolitonVerify.open({...}) -->A hosted ESM build is also available at https://soliton.id/sdk/v1/soliton-web-sdk.esm.js.
04
One call: open() renders the overlay and relays the hosted page’s lifecycle to your callbacks.
import { open } from "@soliton-id/web-sdk";
// Fetch this from YOUR backend, which called POST /api/v1/verifications.
const { url } = await fetch("/your-api/start-verification").then(r => r.json());
const handle = open({
url,
onComplete: ({ verificationId }) => {
// Terminal state (pass OR final fail; not disclosed here). Treat this
// purely as a "check now" signal: have your backend fetch the
// verification id it stored at creation time, never this callback value.
},
onError: ({ code, message }) => {
// link_expired | link_invalid | origin_not_allowed |
// storage_unavailable | internal
},
onClose: () => {
// User dismissed the flow before completion.
},
});
// handle.close() dismisses the overlay programmatically.redirect_url is ignored while embedded; you can set both and the same link works standalone (redirect) or embedded (callbacks).handle.close() dismisses the overlay programmatically and fires onClose if no terminal state was reached.05
Three callbacks cover every runtime outcome.
onCompleteFired once, when the flow reaches a terminal state (pass OR final fail; deliberately not distinguished here). Carries only the verificationId, never a verdict. The overlay closes itself right after.
onErrorA flow-level error (codes below). The overlay stays open so the hosted page can show its own error card; call handle.close() if you want it gone.
onCloseThe user dismissed the flow (the X button, the hosted Cancel, or handle.close()) before completion. Not fired after onComplete.
onError codes:
link_invalidThe URL is not a live verification link: malformed token, or the verification does not exist.
link_expiredThe link's hour passed, or it was already used. Create a fresh verification and open the new url.
origin_not_allowedThe Soliton iframe's direct-parent origin (the page running the SDK) is not registered for your Soliton account. Ask us to register the exact origin (scheme + host + port).
storage_unavailableThe user's browser blocked the session storage the flow needs (fires before camera start, so no capture is lost).
internalWe could not prepare the verification. Safe to create a fresh link and retry.
06
Server-to-server only, exactly like redirect mode.
07
The capture engine never runs on your origin.
Anti-injection and anti-deepfake defenses update with every Soliton deploy, without you shipping anything.
postMessage payloads carry only the verification id. Any machine-readable pass/fail on your page would hand fraudsters an oracle.
The SDK only trusts events from the hosted origin, from its own iframe’s window, carrying the protocol marker; everything else is ignored.