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.
The three steps
Section titled “The three steps”| 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 |
Step 1 - Create a session (backend)
Section titled “Step 1 - Create a session (backend)”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});Step 2 - Mint a browser token (backend)
Section titled “Step 2 - Mint a browser token (backend)”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.
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-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.
npm installexport CHECKTIV_SK_KEY='ah_sk_...' # your secret key, from your environmentexport 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.
Next steps
Section titled “Next steps”- Quickstart - the same flow with the webhook receiver
- Token handoff - the full session and token lifecycle
- Error reference - handle every error code in
onEvent - React - React integration with
<ChecktivProvider>