Skip to content

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-privilege clientToken.
  • 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.

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.

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.

Terminal window
npm install
export CHECKTIV_SK_KEY='ah_sk_...' # your secret key, from your environment
export CHECKTIV_TEMPLATE_ID='wt_...' # a saved workflow template id
export CHECKTIV_PK_KEY='ah_pk_...' # your publishable key
node server.js # serves the page on port 3000

Because 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.

  • 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>