Verification UI & SDK Integration
Choose how users verify: an in-page modal dialog on your site, an embedded widget on your own custom page, a centered popup window, or a full-page hosted redirect.
Integration approaches#
Every verification session created on your server returns a url (for example, https://lkp-three.vercel.app/verify/vs_3f9c...). You can present this verification flow to your users in four ways:
A sleek modal overlay directly over your website. Zero popup blockers, smooth backdrop blur, and mobile responsive bottom sheet.
Host the verification on your own domain (e.g. acme.com/verify or checkout). Mounts inside any container element on your page.
Opens in a centered desktop browser popup window and reports completion back to your opener page via postMessage.
Redirect the browser to session.url. LKP handles everything and redirects the user back to your redirect URL when finished.
What the user sees#
- Your app name and logo (configured in the console).
- Your verification requirements in plain language (e.g. "18 years or older") and an explicit summary of what is kept private.
- Returning users: A single biometric passkey prompt (Touch ID, Face ID, Windows Hello) unlocks their vault, runs the Groth16 zk-SNARK circuit in WebAssembly, and submits the proof in ~40ms.
- First-time users: A simple onboarding prompt to create a passkey vault. If they lack the required credential:
- When
kycWorkflow: "age_estimation": The user takes a 1 to 2 second passive selfie (no identity document required). - When
kycWorkflow: "full_id": The user scans a government ID document (passport, national ID, or driver's license). - When
kycWorkflow: "either"(default): The user chooses between the quick selfie check or government ID scan.
- When
- After issuance, the credential is saved in the user's encrypted vault, the proof verifies, and the user returns to your application.
Option 1: In-page modal overlay#
Include the lightweight SDK script from LKP and call LKP.verify(session.url, { mode: "modal" }) or LKP.modal(session.url). The modal renders over your existing page without leaving your website or opening a separate browser window.
<!-- 1. Include the LKP browser SDK -->
<script src="https://lkp-three.vercel.app/sdk/lkp.js"></script>
<button id="verify-btn">Verify Identity</button>
<script>
document.getElementById("verify-btn").addEventListener("click", async () => {
// 2. Fetch the session URL created by your backend
const session = await fetch("/api/verify/start", { method: "POST" }).then((r) => r.json());
// 3. Open in-page modal dialog
const { sessionId, status } = await LKP.verify(session.url, { mode: "modal" });
// status is "verified", "failed", or "cancelled"
if (status === "verified") {
window.location.href = "/checkout/success?session_id=" + sessionId;
}
});
</script>The modal includes an accessible close button, ESC key handler, click-backdrop-to-close behavior, and smooth mobile bottom-sheet styling.
Option 2: Custom page on your own website#
If you want users to remain completely within your own domain (e.g. https://acme.com/verify or inside a multi-step onboarding wizard), you can mount the verification widget directly inside any element on your page.
Vanilla HTML & JavaScript
<!-- Your custom page container -->
<div class="my-custom-wrapper">
<h2>Verify your eligibility</h2>
<p>Decrypted locally in your browser with zero personal data shared.</p>
<!-- Mount target -->
<div id="lkp-embed-box" style="max-width: 480px; margin: 24px auto;"></div>
</div>
<script src="https://lkp-three.vercel.app/sdk/lkp.js"></script>
<script>
async function initCustomPage() {
const session = await fetch("/api/verify/start", { method: "POST" }).then((r) => r.json());
// Mounts inline and automatically handles iframe sizing and completion
const controller = LKP.mount("#lkp-embed-box", session.url, {
autoResize: true,
onComplete: ({ sessionId, status }) => {
if (status === "verified") {
window.location.href = "/dashboard?verified=" + sessionId;
}
},
});
}
initCustomPage();
</script>React / Next.js Component
import { useEffect, useRef } from "react";
export function CustomVerificationPage({ sessionUrl }: { sessionUrl: string }) {
const containerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (!containerRef.current || !window.LKP) return;
const controller = window.LKP.mount(containerRef.current, sessionUrl, {
autoResize: true,
onComplete: ({ sessionId, status }) => {
if (status === "verified") {
window.location.href = `/verified?session_id=${sessionId}`;
}
},
});
return () => controller.unmount();
}, [sessionUrl]);
return (
<div className="mx-auto max-w-xl py-12 px-4">
<h1 className="text-2xl font-bold">Age Verification</h1>
<div ref={containerRef} className="mt-6 min-h-[540px] w-full" />
</div>
);
}Declarative Web Component
You can also use the drop-in custom element:
<script src="https://lkp-three.vercel.app/sdk/lkp.js"></script>
<lkp-verify
session-url="https://lkpidentity.com/verify/vs_3f9c..."
mode="inline"
auto-resize="true"
></lkp-verify>
<script>
document.querySelector("lkp-verify").addEventListener("lkp-complete", (e) => {
const { sessionId, status } = e.detail;
console.log("Verified:", sessionId, status);
});
</script>Option 3: Popup window#
To open verification in a centered desktop browser popup window:
button.addEventListener("click", async () => {
const session = await fetch("/api/verify/start", { method: "POST" }).then((r) => r.json());
const { sessionId, status } = await LKP.verify(session.url, { mode: "popup" });
if (status === "verified") {
// Confirm on your server with your secret key before granting access
location.href = "/verified?session=" + sessionId;
}
});If the user has an aggressive popup blocker installed that intercepts the window, the SDK automatically falls back to a clean full-page redirect.
Option 4: Full-page redirect#
For maximum simplicity without loading any frontend JavaScript: add your return destination to your app's Redirect URLs in the console, then create the session with a matching redirectUrl:
const session = await lkp("/v1/verification-sessions", {
requirements: [{ kind: "AGE_OVER", minAge: 21 }],
redirectUrl: "https://shop.example/checkout/verified",
});
// Redirect the user
res.redirect(303, session.url);When the session finishes, LKP sends the user back to your URL with two query parameters added:
https://shop.example/checkout/verified?lkp_session=vs_3f9c...&lkp_status=verifiedSDK API reference#
LKP.verify(session, options?)
url string returned by your server."modal". Use "popup" for a popup window, or "redirect" for full-page navigation.{ sessionId, status }.LKP.mount(target, session, options?)
"#verify-container").Mobile and in-app browsers#
WebAuthn passkeys (Face ID, Touch ID, Windows Hello) are supported on all modern platforms (iOS, Android, macOS, Windows, Linux). Certain in-app webviews (such as the embedded browsers inside Instagram, TikTok, WhatsApp, or Telegram) restrict access to hardware passkey APIs.
LKP automatically detects embedded webviews and displays instructions guiding the user to open the verification link in their default browser (Chrome or Safari). For mobile apps, we recommend opening the session URL via Android Custom Tabs or Safari View Controller.
Session lifetime#
- Sessions expire 30 minutes after creation. Expired sessions show as "Abandoned" in the console.
- If a proof fails, the user can retry on the same session until it expires.
- A completed session cannot be reused. Create a new session for each distinct check.