Skip to content

Modules overview

The Checktiv Web SDK (@checktiv/sdk-web) publishes one package with several entry points. The two-line client surface (Checktiv.init(...) then mountProvisioned(...)) covers most integrations. The other subpaths let you register the managed modules, use the React wrapper, or - for teams building a fully custom capture renderer - reach the advanced host-side capture core.

This page is the map of that public surface. Each subpath links to its own guide.

Subpath What it is
. (root) The substrate: Checktiv.init(opts) returns a ChecktivClient with mountProvisioned(...) and a per-module mount(module, opts). Also exports the standalone mount(target, opts), which needs no init() call, and the named mountButton widget. Ships no visual module on its own.
./idv The managed IDV module (camera capture, document scan, liveness). Import it so mount('idv') / mountProvisioned can render it. Heavier than . / ./react (see “Bundle size” below). See Quickstart.
./fraud The consent-gated fraud-signal module. Import it to register it; it stays a no-op until the applicant grants consent. See Fraud consent.
./custom-form The managed custom-form module (author-defined fields on a custom_form step). See Custom forms.
./custom-form/style.css The stylesheet the custom-form module needs. It renders real form controls into your page, so this stylesheet must be present. See Custom forms.
./collect-user-info collectUserInfo({ session }): satisfy a collect_user_info step from your own form. A standalone function, not a mounted module (no UI, no registration import). See Collect user info.
./esign The managed e-signature module (read and sign operator-authored documents on an esign step). Import it to register it. There is no stylesheet to link: the document renders in a hosted frame, and the module injects its own scoped stylesheet for the controls it adds to your page. See “The e-signature module” below.
./credit-history The managed credit-history module (authorization and detail screens on a credit_history step). Import it to register it. There is no stylesheet to link: every screen renders in a hosted frame. A credit_history step is only reachable behind a collect_user_info step, so read “Embedded-workflow renderability” below before you wire it.
./react The React wrapper: ChecktivJourney, ChecktivProvider, ChecktivIdv, useChecktiv(), and useCollectUserInfo(). See React.
./capture Advanced. The Tier-1 headless capture core: createCaptureController. No UI - you render everything. See Advanced capture.
./capture-ui Advanced. The Tier-2 batteries-included renderer: mount(). See Advanced capture.
./capture-ui/style.css The stylesheet the managed IDV capture surface needs. mount('idv') and mountProvisioned render through it, so import it once or the capture screen renders unstyled. See Quickstart.
./workspace The reviewer workspace loader: workspace(config) mounts the hosted review surface for your own staff. Not applicant-facing. See Workspace reviewer.
./cross-device The standalone cross-device handoff panel: mountCrossDevice. See Cross-device handoff.
./idv/cross-device Cross-device overlay orchestration for a custom IDV renderer: openCrossDeviceOverlay, preloadCrossDeviceChunk. See Cross-device handoff.
./agents A machine-readable AI-agent steering manifest (manifest.json). See AI agent steering.

Beyond the mount functions, the . entry exports these values:

  • version - the version string baked into this bundle at build time, also readable as window.Checktiv.version once the CDN tag loads. Log it in a bug report to confirm which bundle a page actually loaded, which is the fastest way to catch a stale CDN cache.
  • CHECKTIV_ERROR_CODES - an array of the journey error codes, the ones that arrive on a mounted module’s error callback. It is not every code the SDK can produce: sessions.create and collectUserInfo().submit() each have their own separate set, and neither appears here. Use this one to check your journey handler covers each case. A later release can add a code, so keep a default branch in any switch over a received error.code. See Error reference.
  • isSessionsCreateError - a type guard for the error that a failed sessions.create call rejects with. Use it to tell a rejected create apart from a thrown programming error, rather than inspecting shape by hand.
  • createChecktivError - builds an error carrying the same shape the SDK emits. Useful when your own wrapper needs to surface a failure through the same handler your SDK errors already flow through.
  • InvalidPublishableKeyError - thrown when a publishable key does not match the expected format, so you can catch a malformed key distinctly from a network failure. It is a format check only: a well-formed key for the wrong region or mode parses without complaint here and is rejected server-side instead.

parsePublishableKey is also currently reachable, both from this entry and as window.Checktiv.parsePublishableKey on the CDN global. It is an internal helper, it is not part of the supported surface, and it is removed in the next major release. Do not import it.

Two further root exports are not listed above because they are documented elsewhere: sessions (see Quickstart) and regionToApiBase.

The managed modules (./idv, ./fraud, ./custom-form, ./esign, ./credit-history) register themselves when you import them. That import is a side effect: importing @checktiv/sdk-web/idv runs a one-time registration so the substrate can resolve 'idv' by name when you call mount('idv') or mountProvisioned.

import { init } from '@checktiv/sdk-web';
import '@checktiv/sdk-web/idv'; // registers the IDV module (side-effect import)
const client = init({ publishableKey: 'ah_pk_us_test_...', getSessionToken });
client.mountProvisioned({ target: document.getElementById('idv-container') });

If a session declares a module you did not import, the mount fails loudly with sdk_load_failed and an actionable message (“import @checktiv/sdk-web/<module> in your app”) rather than a silent blank mount. Import every module your sessions can declare. Because the imports are side effects, keep them: do not let a bundler tree-shake them away.

The root . entry and the ./react entry deliberately do not bundle the managed modules, so a bundler keeps them small. Add the module import yourself.

./idv is the heaviest subpath in the package: importing it adds a runtime schema-validation layer, about 70 KiB gzipped, that the . and ./react entries do not carry. This exists because ./idv parses capture result messages that cross an origin boundary (the capture surface posting results back to your page), and that boundary carries untrusted input, so it is validated at runtime rather than trusted to a compile-time type alone. Every real identity-verification integration imports ./idv, so plan for this cost when sizing your bundle rather than discovering it in a bundle analyzer.

The embedded SDK renders four SDK-renderable applicant steps: id_verification (./idv), custom_form (./custom-form), esign (./esign) and credit_history (./credit-history). It does not render a collect_user_info step (the identity-info collection step). If the workflow also contains an SDK-renderable step, the mount emits a checktiv.idv.error with code sdk_load_failed and stops. If the workflow contains no SDK-renderable step at all (a collect step plus screening checks only), the mount renders nothing and emits no error, so do not wait on an event.

You have three ways to resolve this:

  • Order the workflow template so an SDK-renderable step is first, or
  • Collect identity fields at verification-create time - pass inline applicant fields on the POST /v1/sessions call - and put the verification step first in the workflow template, or
  • Collect the identity fields in your own form and satisfy the step client-side with collectUserInfo before you mount the journey. See Collect user info.

See Quickstart for the verification-create call.

A credit-history workflow always hits this

Section titled “A credit-history workflow always hits this”

Read this before you conclude that adding ./credit-history is enough to make a credit workflow render. For the other three modules the collect-first trap is a workflow you can avoid authoring. For credit_history it is unavoidable by construction: the check declares collect_user_info and a date of birth as required inputs, so a workflow template holding a credit step ALWAYS holds a collect step before it, and the template editor refuses to save one that does not. A credit workflow therefore starts on the one step the SDK does not render, every time.

So one of the three resolutions above is mandatory, not optional, and only two of them apply:

  • Seed the applicant fields at create. Pass the inline applicant fields on POST /v1/sessions, including the date of birth, and the credit step becomes the first step the SDK has to render. This is the least code.
  • Satisfy the step client-side. Call collectUserInfo({ session }) from your own form before you mount the journey. See Collect user info.

Ordering a different SDK-renderable step first is NOT a third option for a credit-only workflow, because the credit step’s inputs still have to come from somewhere: putting id_verification in front makes the mount succeed and leaves the credit step unsatisfiable.

Two field-level requirements come with it, and both fail at collect-submit time rather than at mount:

  • Date of birth is required. The report cannot be ordered without one. A collect step with the date-of-birth field switched off will not save on a template that holds a credit step.
  • The name has to be given names and family name as separate values. A host that sends one typed full-name line gets 422 name_components_required on the collect submit. Nothing can split a verbatim full name into components safely, and guessing which words are the surname orders a credit file on a different person. If you are on an older @checktiv/sdk-web whose form renders a single legal-name box, upgrade before your first credit workflow goes live.

./esign renders an esign step: the applicant reads one or more operator-authored documents, adopts a signature, and then signs or declines. Adopting the signature is also the consent act - confirming the adopt dialog records the electronic-records agreement and the adopted signature together, in that order - so there is no separate “agree to the notice” screen or checkbox to account for. The notice itself is reachable throughout, from a link inside the adopt dialog and from the frame’s own More actions menu.

import { init } from '@checktiv/sdk-web';
import '@checktiv/sdk-web/esign'; // registers the e-signature module (side-effect import)
const client = init({ publishableKey: 'ah_pk_us_test_...', getSessionToken });
client.mountProvisioned({
target: document.getElementById('journey'),
esignCopy: {
// Every key is optional. An absent key falls back to the SDK's English text.
adopt:
'Seleccione la linea de firma en el documento. Anadir su firma tambien registra su acuerdo para usar registros electronicos.',
sign: 'Seleccione Finalizar para aplicar su firma al documento de arriba.',
labels: { finish: 'Finalizar', decline: 'Rechazar' },
shell: { adoptTitle: 'Anada su firma', statusSigned: 'Firmado' },
},
onEvent: (event) => {
if (event.type === 'checktiv.esign.state') renderYourOwnChrome(event.state);
if (event.type === 'checktiv.esign.submitted') advance(event.outcome);
},
});

The document itself renders inside a hosted frame served by Checktiv, not in your page. Your page never receives the document content. The module does append controls of its own to your mount target (the per-document download control, and a state panel), but never the document.

Configuration comes from the signing route, never from /sessions/me. GET /sdk/v1/sessions/me serves an empty config for an esign step on purpose, so there is exactly one casing of every setting and exactly one place that can be stale. Do not read e-signature settings anywhere else.

On the CDN script tag, the module’s code arrives when the step does. The tag registers the module immediately, exactly as before, but the loader itself is a separate file the SDK fetches the first time an esign step mounts, so a page that never reaches one never downloads it. Nothing changes in your integration. If that fetch cannot complete, the module reports sdk_load_failed and paints a short line telling the applicant to reload the page, and esignCopy.error is the key that translates it, the same key every other loading failure uses. See the npm-versus-CDN section above for what to allow in a strict Content Security Policy.

There is no stylesheet to link. Unlike ./custom-form and ./capture-ui, the module injects its own scoped stylesheet into your document, under a ctv-esign- prefix, covering every control it paints on your page. It deliberately never loads the platform’s own global stylesheet, because that would define generic class names such as .btn and .card inside a page it does not own.

The control that applies the adopted signature and submits is Finish, and its label key is labels.finish. The sign ceremony state keeps its name, but no on-screen control says “Sign”. The adopt dialog’s confirm control is separate and is labeled Use this signature (labels.adopt); it adopts a signature and records consent, and it does not sign.

esignCopy is the only way to translate the signing surface, and it is a plain string map you resolve yourself (the SDK ships no internationalization runtime, so nothing here reads a locale). At the top level it carries one key per ceremony state plus frameTitle, and below that four nested maps:

  • the state keys - one per ceremony state, carrying that state’s prose. The states are exported as EsignState and as the ESIGN_STATES array; see “Every ceremony state” below.
  • frameTitle - the signing frame’s accessible name. It is defaulted, and you can override it.
  • labels - the controls and field labels, keyed by EsignLabel.
  • rights - the electronic-records rights panel, keyed by EsignRightsKey.
  • documents - the document download and print controls, keyed by EsignDocumentCopyKey. See “Saving a copy of the document” below.
  • shell - the chrome around the document, keyed by EsignShellKey. This is the largest of the four and the one an integrator most often misses: it holds all three dialogs, the gated Finish control’s per-requirement prose, the in-frame notice view, and the five terminal status words. See “The shell map” below.

Every key in every one of those maps is optional and falls back to English, so a partial map is always safe.

A missing map is not a compile error. TypeScript checks the keys you write; it cannot see that you never assembled shell at all. Omitting a whole map compiles clean and renders English strings inside an otherwise translated ceremony - on the dialogs, which is where the consent sentence and the terminal status words live. If you translate at all, translate all four maps.

EsignShellKey has 24 members. Grouped by what they cover:

Group Keys
The gated Finish control stepsBlocked, gateRead, gateSignature, gateAge
The consent sentence and age attestation consentTermsLink, consentTermsNewWindow, consentAgeAttest, consentError
The adopt-signature dialog adoptTitle, adoptConsent, adoptFinePrint
The decline dialog declineTitle, declineBody
The finish-later dialog finishLaterTitle, finishLaterBody
The in-frame notice view disclosureTitle, disclosureVersionLabel, disclosurePublicCopy, disclosureBack
The terminal status words statusSigned, statusDeclined, statusVoided, statusExpired, statusWithdrawn

Two of these carry more weight than the rest:

  • adoptConsent is the electronic-records consent clause. It is the sentence the applicant agrees to when they confirm the adopt dialog, and its English default names the confirm control by name so that which act does the agreeing is stated rather than inferred. If you translate it, keep that property: name the control, and do not soften it into a bare “I agree”.
  • consentAgeAttest is the only interpolated string in the whole contract. It carries a {count} token in single braces, which the SDK substitutes with the minimum age the operator configured. A translation that drops the token, or that “corrects” it to double braces, is detected and falls back to English rather than rendering a sentence with no number in it.

The notice the signer agrees to tells them they need the ability to print or save a copy of what they signed, so the module renders a download control for each document on the request.

Unlike the signing surface, this control is plain DOM appended to your mount target. It has to be: the download runs on the credential the module holds, and the hosted signing frame deliberately never receives that credential.

  • It appears as soon as the document set is known, and it is still there after the signer has signed, declined or withdrawn, so a copy stays reachable. If the document set can no longer be read (a canceled request, a session past its deadline), the terminal message renders on its own and no download control appears.
  • While a single-document signing frame is on screen it stands down, because the frame’s own top bar carries a save control for that document and two controls for one act, stacked, is noise. It comes back the moment the frame does not have one: after the ceremony ends, when the frame could not open, when the module is reporting a failure over a frame it has disabled, while the signer is withdrawing consent (that surface replaces the frame’s top bar), and on any request carrying more than one document. Do not build a second download control to cover the gap; there is not one.
  • With more than one document you get one control per document, numbered in order.
  • Translate it through esignCopy.documents, which has four keys: download is the control’s label; printAndSignStarted is the line shown after the print-and-sign control hands the signer the file; downloadFailed is the line shown when a download does not complete; and downloadRemoved is the line shown when the stored copy is gone for good.
  • downloadFailed and downloadRemoved are two different answers and you should keep them different. A failed download is retryable, and its line says to select the control again. A removed copy is not: the request succeeded and the bytes no longer exist, because the retention horizon passed or an erasure request was carried out. Telling that signer to try again names an act that can never succeed.
  • A failed download shows that line and changes nothing else. It emits no checktiv.esign.error and never moves the ceremony state, because failing to save a copy is not a failed signing.
  • The line renders on your mount target for every failed download, including one started from the frame’s own save control while this panel’s control is standing down. The frame relays that act to the module and never learns the outcome, so the mount target is the only surface that can report it.
  • The module sets no colors of its own here, so the control inherits your page’s text color and font. It carries data-checktiv-esign-download (whose value is the document id) and sits in a panel marked data-checktiv-esign-documents, so you can target both from your own stylesheet. See “Styling hooks” below for the rest.
Section titled “The signer can withdraw consent or ask for a paper copy”

United States federal law (ESIGN, 15 U.S.C. 7001(c)) gives a consumer who agreed to transact electronically the right to withdraw that consent, and the right to receive the record on paper. The disclosure the signer reads states both rights and names the route for each, so while the signing session is live the module renders those two controls for you. You do not build them, and for as long as they are on screen you must not hide or suppress them.

Both controls live in one place, and only for as long as the session is live:

  • While signing is still open, both controls are inside the signing frame. That is true on the hosted journey and inside your own embed alike; there is no second copy on your mount target.
  • Once the ceremony has ended for this signer (they signed, declined, or withdrew), neither control is rendered, on either surface. What remains is the per-document download control on your mount target, the sealed copy the workflow emails the signer when that step setting is on, and the published disclosure, which states the route for the post-signing window: contact the company that sent the link. The entitlement is undiminished; the on-screen control is not what carries it.

esignStateOffersRights(state) is exported from ./esign and tells you whether a given state offers either control, so your own chrome can react without hardcoding a list of states that will go stale. It returns false for every terminal state, which is the reliable way to see that the controls are gone. The underlying table ships as ESIGN_STATE_RIGHTS if you want to read it directly.

What that means for your integration:

  • Withdrawing consent ends the signing session. The ceremony stops offering to sign, moves to the withdraw state, and you receive checktiv.esign.state carrying it. No checktiv.esign.submitted follows, because nothing was signed. Render your own “we will be in touch about continuing on paper” chrome for that state if you want to, and do not treat it as an error.
  • A signer who withdrew and then comes back arrives on terminal_withdrawn, not withdraw. These are two different states and you need both. withdraw is the live session, in the moment they withdraw. terminal_withdrawn is every visit after that: the module reads the withdrawal off the record, renders that state with no signing frame at all, and never offers to sign again. You also receive terminal_withdrawn if a submit is refused because consent was already withdrawn, for example from a second tab. Binding your chrome only to withdraw leaves a returning signer looking at a state you render nothing for.
  • A paper-copy request is additive: it is passed to your team, and the signer carries on signing. Nothing about your flow changes.
  • Both requests are recorded on the document’s audit trail, and a paper-copy request is surfaced in the console so your team can act on it.
  • Translate the panel through esignCopy.rights. As with every other key, an absent one falls back to English, so a partial map is safe.

The module emits four events, and the union is closed:

Event Payload What it means
checktiv.esign.ready none The module is mounted and running.
checktiv.esign.state state: EsignState The ceremony moved. Emitted on every move.
checktiv.esign.submitted sessionId: string, outcome: 'signed' | 'declined' The step was submitted. Terminal.
checktiv.esign.error error: ChecktivError The module reported a failure. See “Error codes” below.

checktiv.esign.state is how you render your own journey chrome for the states the module owns without making a second call of your own: the module holds the credential, so your page should never re-derive the state.

Not every ceremony ends with checktiv.esign.submitted. A withdrawal ends it with a state change and nothing else, because nothing was signed. Drive your journey off checktiv.esign.state and treat submitted as the signed-or-declined case only.

ESIGN_STATES ships from ./esign as a 23-member array, and EsignState is its union. Check your own handling against the array rather than transcribing this table; it is here so you can see the shape of the state machine at a glance.

“Rights” is what esignStateOffersRights answers: whether the withdraw-consent and paper-copy controls are on screen in that state.

State Rights What the signer is doing or seeing
preparing no The documents are still being frozen. No signing frame yet - see “The states with no signing frame”.
consent yes Server-refusal only. The frame never derives this state; it is reached when a submit is refused for a missing or mismatched consent.
read yes The document is on screen and waiting to be read.
scroll_incomplete yes The read-through requirement is on and the end has not been reached, so Finish is off.
adopt yes No signature has been adopted yet. Selecting the signature line opens the adopt dialog.
age_gate yes A minimum age is set and no date of birth is on file, so the signer must confirm their age.
explain yes Everything is done and the signer is being told what Finish will do.
sign yes Finish is live.
decline yes The signer is giving a reason for declining.
withdraw yes The signer is withdrawing electronic-records consent while the session is still live.
obscured yes Chromium reported the on-screen Finish control as not being painted, so it is off. See “The Finish-button visibility check”.
age_blocked no The signer is below the operator’s minimum age, so this document can never be signed here. The frame stays up with Decline live inside it.
frame_blocked no The signing window never opened. No frame at all, and the panel carries working recovery controls.
conflict no The document changed while the signer had it open.
token_expired no The session credential lapsed and your host must re-mint one. Recoverable. This is not the signing link running out; that is terminal_expired.
protocol_mismatch no The loaded build and the signing frame disagree on the channel version. Terminal and deterministic; only loading a newer build clears it.
error no The load half of a failure: the ceremony could not be resolved, so nothing was ever offered and nothing was signed.
submit_error no The submit half: the signer acted and this build could not confirm the act was recorded. It deliberately does not claim nothing was signed.
terminal_signed no Signed.
terminal_declined no Declined. A normal outcome, not an error.
terminal_voided no The request was canceled and can no longer be signed.
terminal_expired no The time to sign has passed.
terminal_withdrawn no Consent was withdrawn, so nothing was signed and nothing will be.

Several states paint on your mount target with no signing frame on screen at all, so size your container for a panel rather than only for an iframe:

  • preparing is the first paint, and it is a skeleton. Not an alert, not a spinner with a refresh button. The module polls a bounded number of times while the document set freezes, and a reload control only appears once that poll passes half its ceiling or the wait simply runs long - offering one from the first second would invite a signer to cancel a wait that was about to succeed. The skeleton carries data-checktiv-esign-preparing-skeleton.
  • frame_blocked has three working controls and no frame. A reload and a print-and-sign control on the panel, plus the per-document download beside it. All three work with no frame, because the parent plane is the half holding the credential. This is the documented recovery path when a content blocker or a filtering network stops the signing window from opening.
  • The five terminal states paint no frame. The frame is torn down when the ceremony concludes.
  • conflict, protocol_mismatch and error also take the surface back from the frame when the frame is the one that reported them.

token_expired, submit_error and age_blocked are the exceptions worth knowing: none of the three tears a running frame down. age_blocked deliberately leaves it up so Decline stays live inside it, which is the blocked signer’s one remaining exit.

checktiv.esign.error carries a ChecktivError. The module reports a closed set of eight codes from the shared taxonomy, and each one selects the ceremony state whose copy explains it:

error.code State the module moves to
token_expired token_expired
protocol_mismatch protocol_mismatch
submit_failed submit_error
session_expired error
upload_failed error
sdk_load_failed error
session_parked error
wrong_token_type error

Three are worth calling out. wrong_token_type is an integration fault on your side, not a dead session: a client or link token reached a data plane that takes a different credential. submit_failed is the one code that does not land on error: it means the signer acted and the result could not be confirmed, which needs a different sentence from a document that never loaded. And on the CDN script tag, sdk_load_failed also covers the module’s own code not arriving, which is why the sentence it selects asks the applicant to reload rather than describing the document.

Keep a default branch in any switch over error.code. See Error reference for what each code means across the whole SDK.

The module stamps data attributes on everything it paints into your page, so you can target it from your own stylesheet without depending on class names. The two document-panel attributes are the ones most integrations use; the rest are listed so you are not guessing:

Attribute Where
data-checktiv-esign-documents The panel holding the per-document controls
data-checktiv-esign-download One download control; the value is the document id
data-checktiv-esign-download-error The failed-download line
data-checktiv-esign-download-removed The removed-copy line
data-checktiv-esign-shell The module’s own scope container
data-checktiv-esign-panel The state panel
data-checktiv-esign-tone On the state panel; the value is the panel’s tone
data-checktiv-esign-silent On a state panel that is mounted but holding nothing
data-checktiv-esign-terminal-status The terminal status word
data-checktiv-esign-status The state’s sentence
data-checktiv-esign-actions The row of panel controls
data-checktiv-esign-action One panel control; the value is the action name
data-checktiv-esign-preparing-skeleton The preparing skeleton
data-checktiv-esign-print-note The line shown after print-and-sign

Every example above mounts through mountProvisioned, which is what most integrations want. The per-module path is also public and takes the module’s own options:

const handle = client.mount('esign', {
target: document.getElementById('journey'),
copy: esignCopy, // the same EsignCopy map, under `copy` rather than `esignCopy`
onEvent: (event) => {
/* ... */
},
onRequestHelp: (state) => openYourSupportRoute(state),
});
// Idempotent: tears the frame down and stops the transport and the poll.
handle.destroy();

onRequestHelp is the module’s one host-bindable seam, and it exists for white-label integrations. On the hosted journey a signer who lands on a terminal or failed state gets a “Request a new link” control beside the sentence. In your own embed there is no journey around it, so the panel would otherwise show a sentence naming the sender and nothing to press. Registering a handler binds that gap to whatever route you actually have - a support page, your own re-issue flow, a live chat - and the control is painted only when a handler is registered, so an integration that binds nothing keeps today’s sentence rather than gaining a button wired to nothing. The states that offer it are error and the four non-signed terminals; the handler receives the state it was pressed from.

mountProvisioned accepts onRequestHelp directly and forwards it to the module, so you do not have to drop to the per-module path to bind it. It forwards the handler and nothing else: with no handler bound, the SDK still invents no action of its own.

It is not an applicant-initiated “send me a new signing link”. No such route exists, deliberately: it would be an unauthenticated re-issue trigger on an expired credential.

The Finish-button visibility check is Chromium-only

Section titled “The Finish-button visibility check is Chromium-only”

The signing frame observes its own Finish control with IntersectionObserver v2 and turns the action off while the browser reports the control as covered by something else on the page. It does not react to the control being scrolled out of view: off screen is not covered, and telling a signer to scroll when the real problem is an overlay sends them to fix the wrong thing.

trackVisibility is Chromium-only: on Safari and Firefox the browser reports nothing and this check does nothing at all, which on mobile is a large share of real traffic. Treat it as defense in depth, never as a security boundary, and never as protection against clickjacking on non-Chromium traffic. If your page paints over the frame, Chromium users will see the control turn off with an explanation.

npm - install the package and import the subpaths you use:

Terminal window
npm install @checktiv/sdk-web
import { init } from '@checktiv/sdk-web';
import '@checktiv/sdk-web/idv';

Each subpath is independently tree-shakeable, so a ./idv-only integration never pulls in ./fraud.

CDN - one script tag, and every module is registered for you:

<script src="https://sdk.us.checktiv.com/v1/sdk.js" crossorigin="anonymous"></script>

The CDN bundle registers ./idv, ./fraud, ./custom-form, ./esign and ./credit-history eagerly, so there is no per-module import to add. After the tag loads, the SDK is available as window.Checktiv. For production, pin a version and add a Subresource Integrity hash. See Versioning and the release notes.

Registration is eager; weight is not. Every module ships its code as a separate file that the SDK fetches from the same origin the first time that module mounts, so a page that never reaches an identity-verification, fraud, custom-form, e-signature or credit-history step never downloads one. Identity verification is by far the largest of them, so the script tag itself is now a small file. The e-signature module splits again on the same principle: the part that paints the first status line arrives when the step is reached, and the part that talks to the signing frame arrives once the server has answered. There is nothing extra to import and nothing to configure. The SDK also links its own stylesheet from the same origin the first time a step renders, so the capture frame and the custom-form fields are styled without you adding a <link>. The one thing to check is your Content Security Policy: all of this is fetched at runtime and does not carry your page nonce, so a strict nonce-only script-src has to allow the SDK origin, and style-src has to allow it too. See Security headers.

A test-mode publishable key with the synthetic driver runs the full checktiv.idv.* event flow locally with no camera and no capture license. See the local-development section of the Quickstart.