The SDK follows semantic versioning. This page describes how to pin safely, what compatibility is guaranteed between the CDN bundle and the embed capture frame, and the non-breaking evolution contract that governs what can change without a major version bump.
Pinning
Section titled “Pinning”npm: pin to an exact version in package.json:
{ "dependencies": { "@checktiv/sdk-web": "<version>" }}Replace <version> with the exact version you tested against. The release notes list every published version.
Do not use ^ or ~ in production. A patch release may change behavior in ways you have not tested.
CDN: always pin the version in the URL and supply a Subresource Integrity hash:
<script src="https://sdk.us.checktiv.com/sdk/<version>/sdk.js" integrity="sha384-<hash>" crossorigin="anonymous"></script>Derive the hash from the pinned bundle itself, once for each version you pin:
set -o pipefailcurl --fail --show-error --location --silent \ "https://sdk.us.checktiv.com/sdk/<version>/sdk.js" \ | openssl dgst -sha384 -binary | openssl base64 -AThe --fail and pipefail are load-bearing, not caution. A version published moments ago returns a 404 until its pinned copy finishes propagating, and a plain curl -s exits successfully on that 404 and pipes the error body, or nothing at all, straight into the digest. You get a well-formed hash of the wrong bytes, pin it, and every page load then fails the integrity check with no clue why. With these flags the command fails instead, and you rerun it once the pinned URL serves the bundle.
Loading sdk.js without an SRI hash or from an un-pinned URL exposes you to CDN compromise; a tampered bundle would run with full page access.
What the pin covers, and what the hash covers
Section titled “What the pin covers, and what the hash covers”These are two different controls, and the second is narrower than the first.
The pin covers the whole release. A pinned sdk.js loads its module files from the same pinned path it was served from, so /sdk/<version>/sdk.js fetches /sdk/<version>/ module files and a stylesheet from that same immutable version. Nothing you pin moves underneath you.
The SRI hash covers the file you put it on. The integrity attribute verifies sdk.js itself. The module files the SDK loads at runtime are fetched by the browser’s dynamic import(), which has no integrity attribute, so they are not covered by that hash. They are pinned and immutable, but they are not hash-verified. If your threat model requires end-to-end verification of every byte, treat the pin as the control and the hash as covering the entry point only.
If you proxy or self-host the bundle
Section titled “If you proxy or self-host the bundle”Some teams serve the SDK from their own origin instead of loading it from ours. If you do, serve the module files that sit beside it too. The SDK resolves them relative to the URL your page loaded sdk.js from, so a proxy that mirrors only sdk.js will have every module fail at mount, with the page itself loading normally.
Mirror the whole directory the bundle came from, at the same relative paths. That is sdk.js, sdk.css, and every other .js file published alongside them for that version. Verify by loading a page that reaches each step you use and confirming no request 404s.
SDK-to-embed compatibility window
Section titled “SDK-to-embed compatibility window”The SDK (the JavaScript you install) communicates with the capture frame (served by the platform) over a versioned postMessage protocol. The platform guarantees backwards compatibility for at least two prior SDK major versions. You will not be stranded by a platform update.
When the protocol advances (a v2 prefix), the platform serves both v1 and v2 simultaneously during a transition window. SDK releases that ship during the window accept either. You will be notified in the release notes when a version is scheduled for retirement.
The non-breaking evolution contract
Section titled “The non-breaking evolution contract”The following changes may be made without a major version bump:
1. Additive bt_* scopes
Section titled “1. Additive bt_* scopes”New optional scopes may be added to bt_* tokens. An old token that was minted without a new scope continues to work for the routes it was originally valid for. Your mint endpoint does not need changes until you adopt a feature that requires the new scope.
2. Additive event namespaces
Section titled “2. Additive event namespaces”New event types may be added at any time. The event stream is forward-open: checktiv.${string} is the tail, so any string beginning with checktiv. is a valid event type. Event literals are never reused with a different meaning.
Do not exhaustively switch on event.type. Unknown event types may arrive without a major version bump. Always include a default case or an else branch:
onEvent: (event) => { if (event.type === 'checktiv.idv.submitted') { // handle } else if (event.type === 'checktiv.idv.error') { // handle } // Unknown events: ignore silently.};3. Additive error codes
Section titled “3. Additive error codes”New error.code values may be added in a minor release. The published ChecktivErrorCode type is forward-open in the same way as the event stream: it lists every code in the error reference and accepts a code that a later release adds. Codes are never reused with a different meaning, and each new code is announced in the release notes.
Do not exhaustively switch on error.code. Always include a default case. In that case, use the two fields that stay meaningful for a code you do not recognize:
error.messageis an actionable, user-visible sentence for every code, present and future. Show it.error.recoveryis a CLOSED set of four values (retry,cross_device,refresh_session,contact_operator). A new code always uses one of them, so branch your recovery logic onrecoveryand your special cases oncode.
if (event.type === 'checktiv.idv.error') { const { code, recovery, message } = event.error; switch (code) { case 'camera_denied': showCameraPermissionHelp(); break; default: // A code this build does not know. Show the message, then act on recovery. showGenericErrorMessage(message); } if (recovery === 'refresh_session') restartVerification();}If you keep your own text for each code (a translation catalog, for example), give the miss case its own generic string. Do not fall back to the text of a specific code: a new code would then show a confident but incorrect explanation of what failed.
TypeScript users who want the compiler to flag every new code can annotate against ChecktivKnownErrorCode instead. It is the closed set of codes in the version you installed, so a version bump that adds a code becomes a compile error you resolve deliberately. It is opt-in for this reason. Use ChecktivErrorCode for a value you receive.
4. checktiv.idv.submitted is frozen
Section titled “4. checktiv.idv.submitted is frozen”The checktiv.idv.submitted event will never gain a decision or verdict field. It signals that capture is complete, nothing more. The verdict arrives via kyc.session.* webhook. See Verdict and webhooks.
5. Managed-to-headless migration path
Section titled “5. Managed-to-headless migration path”A customer can move from the server-driven mountProvisioned path to the explicit mount('idv', opts) override, or to the headless createCaptureController kernel, without a contract break. The session, token, and event contracts are identical across all three mount paths.
6. Removal of a deprecated returned field
Section titled “6. Removal of a deprecated returned field”Every other clause on this page permits an ADDITION. This clause is the single exception in the other direction: the one circumstance in which a minor release takes something away.
A field on an object that an SDK method returns may be removed in a minor release, and only if all of these were true from at least one full minor release before the removing release:
- It was named as deprecated on this site, on the page that documents it.
- Its removal release was named there too, as a specific version.
Scope, stated once. “A field on an object that an SDK method returns” is the whole of it. These are outside this clause and removing any of them remains a major-version change:
- A parameter you pass in.
- An export, whatever its documented support status.
- Any field of the error object, including
recovery. Clause 3 tells you to branch on those, so this clause must not reach them. - Any field of a
checktiv.*event payload, includingchecktiv.idv.submitted, which clause 4 freezes outright. - A change to an existing field’s type or meaning, which is never in scope here and never permitted in a minor.
Reading a deprecated field keeps working for the whole notice period, so the migration is: stop branching on it, then upgrade.
A deprecation will not appear in your editor. The published type declarations ship without comments, deliberately, so there is no strikethrough and no tooltip for a deprecated field. This site and the release notes are the only notice channels. That is precisely why this clause requires a full minor release of written notice with a named removal version, rather than relying on tooling to tell you.
Currently under this clause:
describe().captureStructuredName, deprecated in 1.8.0, removed in 1.10.0. It reports what the workflow template declares, never what the server enforces, and its default isfalse, so it readsfalseeven on a workflow whose background check cannot run without the name components. ReadnameComponentsinstead. See Collect user info.
Pre-GA features (betas)
Section titled “Pre-GA features (betas)”Some features are available before general availability via opt-in flags:
Checktiv.init({ publishableKey: 'ah_pk_us_test_...', getSessionToken: async (ctx) => { /* ... */ }, betas: ['idv_enhanced_coaching_v1'],});Unknown entries in betas are silently ignored; a future beta name is forward-compatible. Pre-GA surfaces are exempt from the additive-only guarantee until they reach GA. Breaking changes to a pre-GA feature are announced in the release notes with at least one release of notice.
Related pages
Section titled “Related pages”- Release notes - changelog, published versions, and retirement notices
- Secret key rotation - rotate your
ah_sk_*key independently of SDK versions - Error reference -
protocol_mismatchand what it means in practice