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 three steps, and it hinges on one split of responsibility:

  • Your backend holds the secret key (ah_sk_...) and does the two things the browser must never do: create a session and mint short-lived browser tokens.
  • 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. The SDK owns refresh: whenever it needs a token it calls your getSessionToken callback, which asks your backend for a fresh one.

Step Where Call
1 backend Create a session (POST /v1/sessions)
2 backend Mint a browser token (.../browser_token)
3 frontend Checktiv.init(...) then mountProvisioned

Your secret key must never reach the browser. Create the session on your server and return only the session id (a vs_... string). Every response is wrapped in a data envelope, so the id is at data.id.

// POST /api/session (your backend)
app.post('/api/session', async (_req, res) => {
const response = await fetch('https://api.us.checktiv.com/v1/sessions', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CHECKTIV_SECRET_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
// `checks` is an array of check-type strings. `id_verification`
// renders the managed document + selfie flow in the SDK.
checks: ['id_verification'],
// Inline applicant details for a new applicant. Every field is optional.
applicant: { first_name: 'Ada', last_name: 'Lovelace', email: 'ada@example.com' },
}),
});
const { data } = await response.json();
res.json({ sessionId: data.id }); // a `vs_...` string
});

The SDK calls your getSessionToken callback whenever it needs a bt_... token. Back it with a mint endpoint. This route takes no request body, and the token is returned at data.browser_token.

// GET /api/token?sessionId=... (your backend)
app.get('/api/token', async (req, res) => {
const { sessionId } = req.query; // the `vs_...` id from Step 1
const response = await fetch(
`https://api.us.checktiv.com/v1/sessions/${sessionId}/browser_token`,
{
method: 'POST',
headers: { Authorization: `Bearer ${process.env.CHECKTIV_SECRET_KEY}` },
},
);
const { data } = await response.json();
res.set('Cache-Control', 'no-store'); // never cache a short-lived token
res.json({ token: data.browser_token }); // a `bt_...` string
});

The bt_... token is short-lived. You never schedule a refresh yourself: the SDK calls getSessionToken again when it needs a new one.

Step 3 - Load and mount the SDK (frontend)

Section titled “Step 3 - 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 the SRI hash from the release notes.

Then initialize the client with your publishable key plus a getSessionToken callback that fetches a fresh token from your backend, and mount into a target element:

<div id="checktiv"></div>
<script>
const client = Checktiv.init({
publishableKey: 'ah_pk_us_test_...', // public by design (origin-pinned)
getSessionToken: function () {
// Ask YOUR backend for a fresh browser token. This is the whole token
// lifecycle - the SDK calls this whenever it needs one.
return fetch('/api/token?sessionId=' + encodeURIComponent(sessionId))
.then(function (r) {
return r.json();
})
.then(function (d) {
return d.token;
});
},
});
// `mountProvisioned` reads the modules the server declared for the session
// and renders them. For an `id_verification` session that is the managed IDV
// (document + selfie) capture flow. The secret key is never in this code.
client.mountProvisioned({
target: document.getElementById('checktiv'),
onEvent: function (event) {
if (event.type === 'checktiv.idv.submitted') {
// Capture finished. Wait for the webhook verdict before acting on it.
}
},
});
</script>

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-quickstart: 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_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.