Skip to main content

Web SDK Integration Guide

Documented versions. Web SDK 1.0.1 (default) / 1.0.0-beta.3 (@beta). This page follows the WIDTH integration guide dated 11 September 2026. Confirm the selected SDK build and its target environment before deployment.

Overview

The Width Web SDK provides a drop-in identity verification flow for web apps. It supports KYC and KYB workflows including document capture, liveness detection, and review. The SDK is self-hosted: you import one package, configure it with a fluent chain, and call launch() — the SDK presents its own modal or new tab and reports the outcome through events. No DOM container is required. It ships as an ESM build with full TypeScript types and no peer dependencies.

Requirements

Requirement Minimum
Module format ESM (import)
TypeScript Types included (optional)
Peer dependencies None
Backend Must issue a session token via sdk.credentials

Step 1: Get a session token (backend)

The Web SDK starts from a one-time session token (the sdkToken). Your server requests it from Width's sdk.credentials endpoint, then hands it to the browser. The public apiKey and domainId are configured in the next steps; the secret signing happens only on your server.

// Server-side request to Width (signed with your secret)
POST {WIDTH_GATEWAY_URL}/gateway?api=sdk.credentials&apiVersion=1.0.0&apiKey={your_apiKey}&sign=...
Content-Type: application/json

{}
// Response — read sdkToken and the domains[] list
{
  "apiCode": 200,
  "apiMsg": "OK",
  "data": {
    "data": {
      "sdkToken": "eyJhbGciOi...",
      "domains": [
        { "code": "DEFAULT", "name": "Default", "id": "dom_xxxxxxxx" }
      ]
    }
  }
}

Expose a small backend route (for example /your-backend/sdk-token) that returns the sdkToken and the domainId to your frontend. Pass the chosen domainId from domains[] into the SDK config in Step 3.

Each token is single-use Each sdkToken is single-use. Reissue a new token whenever a user starts a new verification — do not cache or reuse it.
The sdk.credentials call uses the gateway base URL and HMAC signing documented in the OpenAPI reference.

Step 2: Install the SDK

Install from npm. After install, import by the package name @width-com/web-sdk — ESM build, TypeScript types included, no peer dependencies. The tag you install also selects the environment the SDK connects to: in this documentation baseline, the default (latest) release targets production and the @beta tag targets the platform (testing) environment. Install the tag that matches the environment your apiKey belongs to, and confirm which address your installed build opens before you ship (see the note below).

# Production (正式环境)
npm install @width-com/web-sdk
# or
pnpm add @width-com/web-sdk
# or
yarn add @width-com/web-sdk

# Testing / beta (测试环境)
npm install @width-com/web-sdk@beta
# or
pnpm add @width-com/web-sdk@beta
# or
yarn add @width-com/web-sdk@beta
The tag selects the environment — a mismatch shows Invalid apiKey The verification page address is compiled into each published version, so the environment is fixed at install time and withConf carries no field that changes it. in this documentation baseline, the default (latest) release opens https://identity.width.com/sdk-page (production) and @beta opens https://identity-platform.width.com/sdk-page (platform) — that pairing reflects the builds the two tags pointed at in the guide, not a fixed rule, so check which host your installed build opens before you ship. If the tag does not match the environment your apiKey belongs to, that page renders Invalid apiKey and the flow stops there — backend OpenAPI calls made with the same key are unaffected, so the key itself is not the problem. Choose the tag by environment, not by version number. One key covers both sides: withConf declares a single key field, apiKey, and it is the same key you sign sdk.credentials with — the TypeScript declarations shipped inside the package list every field it accepts. For reference, @beta is recorded here as 1.0.0-beta.3 (published 2026-08-05) and the default to 1.0.1; both carry eventType and language.

Step 3: Initialize (Quick Start)

Build the verification with a fluent chain: init the session token, withConf the configuration, subscribe to events with on / onMessage, then build().launch(). launch() presents the self-hosted modal or new tab — you do not pass a DOM container.

import { widthWebSdk } from '@width-com/web-sdk';

// Get the session token from your backend (sdkToken from sdk.credentials)
const { sdkToken, domainId } =
  await fetch('/your-backend/sdk-token').then(r => r.json());

widthWebSdk
  .init(sdkToken)
  .withConf({
    apiKey:     'ak_your_public_api_key', // required
    domainId,                             // required
    email:      'enduser@example.com',    // required
    customerId: 'cust_internal_id',       // required
    mode:       'modal',                  // 'modal' (default) | 'newtab'
    // eventType: 'KYC_EVENT',            // optional — 'KYC_EVENT' | 'KYB_EVENT'; omit for default routing
  })
  .on('idCheck.onApplicantVerificationCompleted', (payload) => {
    // payload.reviewResult.reviewAnswer: 'GREEN' (approved) | 'RED' (not approved)
  })
  .on('idCheck.onApplicantSubmitted', (payload) => {
    // Submitted; async review in progress
  })
  .on('idCheck.onError', (payload) => {
    // payload.code / payload.error
  })
  .onMessage((type, payload) => {
    if (type === 'width.onCancelled') {
      // User closed the flow
    }
  })
  .build()
  .launch();   // self-hosted modal / new tab — no DOM container needed
eventType and language require 1.0.1 or later Both fields are available from 1.0.1 (released 2026-08-20), the documented default (latest) release, and from 1.0.0-beta.3, which the @beta tag is recorded here as — so both environments have them, and you still pick the tag by environment (see Step 2). Stable releases up to 1.0.0 do not contain them: a TypeScript project fails to compile, and a plain JavaScript project ignores them without any error. eventType accepts KYC_EVENT or KYB_EVENT, language accepts en or zh; both are optional, and omitting them keeps the default behaviour.

Configuration (withConf)

Field Type Required Description
apiKey string Yes Public Width API Key (ak_...)
domainId string Yes Business domain GID, from domains[] in sdk.credentials
email string Yes End-user email
customerId string Yes Your internal user ID
mode 'modal' | 'newtab' No Presentation, default 'modal'
language 'en' | 'zh' No Interface language. Omit it and the interface is English; the browser language is not detected automatically. Available from 1.0.1.
eventType 'KYC_EVENT' | 'KYB_EVENT' No Which flow to run — individual verification (KYC_EVENT) or corporate verification (KYB_EVENT). Omit the field to keep the default routing (see Workflows & the Decision Model). Available from 1.0.1.
zIndex number No No default is specified in this documentation baseline.Modal stacking order

Step 4: Handle the result

launch() returns the full verification-page URL for this session on success, and an empty string on failure. Errors are delivered through idCheck.onError.

Branch on the events you subscribed to. The final verdict is carried by reviewResult.reviewAnswer. It takes exactly two values: GREEN when the outcome is approved, and RED for every other outcome.

.on('idCheck.onApplicantVerificationCompleted', (payload) => {
  const { reviewResult, executionId } = payload;
  if (reviewResult.reviewAnswer === 'GREEN') {
    grantAccess(executionId);
  } else {
    handleNotApproved(executionId);
  }
})

Each event maps to a branch of your handling logic:

Event Handle
idCheck.onApplicantVerificationCompleted Final verdict available — read reviewAnswer
idCheck.onApplicantSubmitted Submitted; enters async review
idCheck.onError Report the failure
width.onCancelled (via onMessage) User closed the flow
Reconciliation executionId is the handle for matching against your backend / OpenAPI. Persist it on idCheck.onApplicantSubmitted (async review) so your server can poll the final status later.

Verification result reference

The final verdict is carried by reviewResult.reviewAnswer:

reviewAnswer Meaning
GREEN Approved
RED Not approved. The SDK maps every outcome other than an approval to RED — a rejection lands here, and so does any other non-approved result. Before treating RED as a rejection, read the case status (caseStatus from workflow.execution.status, or the case in the Console).
Two values only As of 1.0.1 (and 1.0.0-beta.3), the Web SDK reduces the decision to these two values; there is no third value. Branch on GREEN for approval, and treat RED as “not approved” rather than “rejected” unless the case status confirms a rejection.

Methods

Call Returns Purpose
widthWebSdk.init(accessToken) builder Start the chain with the session token
.withConf(conf) builder Provide configuration (see Configuration)
.on(eventName, callback) builder Subscribe to a single event
.onMessage((type, payload) => {}) builder Receive all events with one handler
.build() instance Finish building
instance.launch(container?) string Start verification; returns the full verification-page URL for this session — of the form https://identity.width.com/sdk-page?sdkMode=1&sdkSession=<session id>&apiKey=<your apiKey>&theme=light, with the host taken from the build you installed (see Step 2) — and '' when the launch fails. The URL addresses this one verification session; the session token behind it is single-use (see Step 1). container is ignored
instance.close() void Programmatically close the verification window

Events

Subscribe to a single event with .on(eventName, callback), or receive all with .onMessage((type, payload) => {}).

Event When Payload
idCheck.onApplicantVerificationCompleted Flow complete, has a verdict { reviewStatus: 'completed', reviewResult: { reviewAnswer: 'GREEN' | 'RED' }, executionId }
idCheck.onApplicantStatusChanged Fires together with Completed { reviewStatus: 'completed', reviewResult: { reviewAnswer: 'GREEN' | 'RED' }, executionId }
idCheck.onApplicantSubmitted Submitted, async review { executionId }
idCheck.onError Failure { code, error, executionId? }
width.onCancelled User closed (via onMessage only) {}
Errors Validation failures and runtime errors are all delivered via idCheck.onError — the SDK does not throw to your code.

Error Codes

On idCheck.onError, payload.code is one of:

Code Meaning Retryable
INVALID_INPUT Launch parameter missing or invalid No
SDK_ERROR Network, timeout, or internal error Yes
WORKFLOW_FAILED Flow ended in a non-success state No

Browser Support

The Web SDK relies on crypto.randomUUID, ES2020 syntax, and a modern fetch / postMessage. The versions below are the earliest that carry all three; anything older is not supported.

Browser Minimum version
Chrome / Edge (desktop) 92+
Firefox 95+
Safari (macOS and iOS) 15.4+
Chrome (Android) 92+

Troubleshooting

Symptom Cause Resolution
onError { INVALID_INPUT, "apiKey is required." } apiKey missing or empty Pass the public API Key
onError { INVALID_INPUT, "accessToken is required." } Backend didn't return sdkToken Check the sdk.credentials integration
onError { INVALID_INPUT, "domainId is required." } domainId not passed in withConf Pass the id of the domain you picked from domains[] in the sdk.credentials response
onError { INVALID_INPUT, "email is required." } Missing email Pass the end-user email
onError { INVALID_INPUT, "customerId is required." } Missing customerId Pass the internal user ID
onError { INVALID_INPUT, "email format is invalid." } Invalid email format Validate in your form first
onError { INVALID_INPUT, "apiKey does not match SDK credentials." } apiKey and accessToken belong to different apps Reissue the token with the same apiKey
onError { SDK_ERROR } Network / connection issue Rebuild the chain and launch() again
Modal won't appear / iframe blocked Domain not allowlisted, or CSP frame-src missing the origin Add the origin under API & Integration in the Console (see the note below the table), then allow it in your CSP frame-src
Camera permission denied Host Permissions-Policy is too strict Update your policy headers (see note below)
No events fire Session still ongoing, or callbacks not registered Wait for completion; confirm your .on() callbacks
Allowlist your site in the Console The allowlist is yours to configure: add every origin that hosts the SDK under API & Integration in the Console. Each entry must start with https:// or http://, carry no path, and use no wildcards, so list each host on its own line (for example https://app.example.com and https://checkout.example.com). If the button that generates a key is not on that page, the role on your account is missing the manage API keys permission — those controls render by permission, so they are hidden rather than greyed out; ask a tenant administrator to grant it.
CSP & Permissions-Policy In modal mode the SDK inserts an iframe that loads the verification page into your own page, so if your page sets a Content-Security-Policy you must allow that origin under frame-src, and grant camera (plus the microphone / motion sensors used by liveness) to the same origin via Permissions-Policy. Which origin to write depends on the tag you installed (see Step 2): the default (latest, baseline 1.0.1) build loads https://identity.width.com, and the @beta build loads https://identity-platform.width.com. Send the block that matches the build you ship — if the origin is absent from frame-src, the browser blocks the iframe and the verification window stays blank.
# Default (latest) build / 默认(latest)版
Content-Security-Policy: frame-src https://identity.width.com;
Permissions-Policy: camera=(self "https://identity.width.com"),
                    microphone=(self "https://identity.width.com"),
                    accelerometer=(self "https://identity.width.com"),
                    gyroscope=(self "https://identity.width.com")
# @beta build / @beta 版
Content-Security-Policy: frame-src https://identity-platform.width.com;
Permissions-Policy: camera=(self "https://identity-platform.width.com"),
                    microphone=(self "https://identity-platform.width.com"),
                    accelerometer=(self "https://identity-platform.width.com"),
                    gyroscope=(self "https://identity-platform.width.com")