Sample app - own-page IDV
This is the runnable companion to the Quickstart: a tiny Express server and a single HTML page that embed identity verification directly in your own page. It is deliberately small so you can read the whole integration top to bottom, then adapt it to your stack.
The integration is two steps, and it hinges on one split of responsibility:
- Your backend holds the secret key (
ah_sk_...) and does the one thing the browser must never do: create a session and return a durable, low-privilegeclientToken. - Your frontend loads the SDK with the publishable key (
ah_pk_..., safe to expose because it is origin-pinned) and renders the verification.
You write no token-lifecycle code. mount() calls your fetchToken callback
ONCE at the cold start to get the clientToken, then exchanges it for
short-lived working tokens internally and refreshes them silently.
The two steps
Section titled “The two steps”| Step | Where | Call |
|---|---|---|
| 1 | backend | Create a session and return its clientToken |
| 2 | frontend | Checktiv.mount(target, { fetchToken, ... }) |
Step 1 - Create the session and return a clientToken (backend)
Section titled “Step 1 - Create the session and return a clientToken (backend)”Your secret key must never reach the browser. Create the session on your server
using the SDK’s sessions.create helper and return only the durable
clientToken - it can resume the applicant’s own in-progress session but can
never read results or PII.
// GET /api/client-token (your backend)import { sessions, regionToApiBase } from '@checktiv/sdk-web';
// This route mints a durable, resume-capable token, so it must sit behind// your own authentication (session cookie, JWT, etc.) - never expose it as a// public, unauthenticated endpoint.app.get('/api/client-token', async (req, res) => { const { clientToken } = await sessions.create( { // `templateId` is a saved workflow template id (`wt_...`). The workflow // template decides which modules run; the SDK renders whatever the // server declares for the session. templateId: 'wt_01abc', // Inline applicant details for a new applicant. Every field is optional. // The name is `familyName` plus an ordered `givenNames` array. applicant: { familyName: 'Lovelace', givenNames: ['Ada'], email: 'ada@example.com' }, }, { apiBase: regionToApiBase('us'), // or 'eu' for your account's region secretKey: process.env.CHECKTIV_SK_KEY, // ah_sk_... - never in the browser // One stable key PER logical create (here, the authenticated caller's // id); use a fresh key for a distinct applicant. The same key with the // same body replays the original response; the same key with a // different body returns 409 idempotency_conflict. idempotencyKey: req.user.id, }, ); res.set('Cache-Control', 'no-store'); // never cache a resume-capable token res.json({ clientToken });});sessions.create throws if it runs in a browser, so call it only from your
server. Because the clientToken is durable and resume-capable, the sample
above derives the idempotency key from the authenticated caller (req.user.id)
rather than a client-supplied value - see the
Quickstart
for the full security note on authenticating this endpoint.
Step 2 - Load and mount the SDK (frontend)
Section titled “Step 2 - Load and mount the SDK (frontend)”Load the SDK bundle from the CDN. It exposes a global Checktiv.
<script src="https://sdk.us.checktiv.com/v1/sdk.js" crossorigin="anonymous"></script>This loads the latest release. For production, pin to a specific version with a
Subresource Integrity hash instead. SRI requires the immutable pinned URL
(/sdk/<ver>/sdk.js), not the moving /v1/sdk.js pointer. See
Versioning for the pin URL shape and how to
derive the SRI hash.
Then mount into a target element, with a fetchToken callback that asks your
backend for the clientToken:
<div id="checktiv"></div>
<script> const handle = Checktiv.mount(document.getElementById('checktiv'), { publishableKey: 'ah_pk_us_live_...', // public by design (origin-pinned) // Called ONCE at the cold start. The SDK exchanges the clientToken for // short-lived working tokens internally and refreshes them silently - // this is the whole token lifecycle, and you write none of it. fetchToken: function () { return fetch('/api/client-token') .then(function (r) { return r.json(); }) .then(function (d) { return d.clientToken; }); }, onEvent: function (event) { if (event.type === 'checktiv.idv.submitted') { // Capture finished. Wait for the webhook verdict before acting on it. } }, });</script>mount() reads the modules the server declared for the session and renders
them. For a template whose first applicant step is id_verification that is
the managed IDV (document + selfie) capture flow. The secret key is never in
this code.
checktiv.idv.submitted means capture finished, not that verification passed.
The authoritative verdict arrives on your server as a signed webhook. See
Verdict and webhooks for the receiver
and signature verification.
Run the full sample
Section titled “Run the full sample”The complete runnable version of this walkthrough ships as an SDK example named
idv-sdk-zerolifecycle: one Express server file, one HTML file, a
.env.example, and a run script. It reads the secret key from the
CHECKTIV_SK_KEY environment variable, never from a file, and injects the
publishable key into the page at render time.
npm installexport CHECKTIV_SK_KEY='ah_sk_...' # your secret key, from your environmentexport CHECKTIV_TEMPLATE_ID='wt_...' # a saved workflow template idexport CHECKTIV_PK_KEY='ah_pk_...' # your publishable keynode server.js # serves the page on port 3000Because the publishable key is origin-pinned, open the page through the origin
your key is registered for, not bare http://localhost.
Its sibling, idv-sdk-quickstart, runs the same integration with the
alternative per-request bt_* flow
instead - read the two side by side if you need that pattern.
Next steps
Section titled “Next steps”- Quickstart - the same flow with the webhook receiver, plus the alternative per-request token path
- Token handoff - the full session and token lifecycle, including both token shapes
- Error reference - handle every error code in
onEvent - React - React integration:
<ChecktivJourney>and<ChecktivProvider>