Web SDK
Drop-in verification flow for web apps. Modal or new-tab presentation, full TypeScript types, no peer dependencies. Requires a one-time session token from your backend.
About this SDK
The Width Web SDK is a drop-in verification experience for KYC, KYB, and one-off liveness in your web app. ESM build, full TypeScript types, no peer dependencies.
| Feature | Supported |
|---|---|
| KYC | ✓ |
| KYB | ✓ |
| One-off liveness | ✓ |
| TypeScript types | ✓ |
1. Generate an access token (backend)
Call sdk.credentials from your server using the OpenAPI signing scheme:
POST {WIDTH_GATEWAY_URL}/gateway?api=sdk.credentials&apiVersion=1.0.0&apiKey={your_apiKey}&sign=...
Content-Type: application/json
{}
{
"apiCode": 200,
"apiMsg": "OK",
"data": {
"data": {
"sdkToken": "eyJhbGciOi...",
"domains": [
{ "code": "DEFAULT", "name": "Default", "id": "dom_xxxxxxxx" }
]
}
}
}
sdkToken is single-use. Reissue a new token whenever a user starts a new verification.2. Install the SDK
# Production npm install @width-com/web-sdk # or pnpm add @width-com/web-sdk # or yarn add @width-com/web-sdk # Platform (testing) 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 you install also selects the environment the SDK connects to:
the default (latest) release targets production, and the @beta tag
targets the platform (testing) environment. The target is compiled into
the package and cannot be changed at runtime, so install the tag that
matches the environment your apiKey belongs to. A mismatch is not a
silent failure: the verification page will render Invalid apiKey even
though the same key works against the backend API.
3. Initialize
import { widthWebSdk } from '@width-com/web-sdk';
const { sdkToken, domainId } =
await fetch('/your-backend/sdk-token').then(r => r.json());
widthWebSdk
.init(sdkToken)
.withConf({
apiKey: 'ak_your_public_api_key',
domainId,
email: 'enduser@example.com',
customerId: 'cust_internal_id',
mode: 'modal',
})
.on('idCheck.onApplicantVerificationCompleted', (payload) => {
// handle the outcome
})
.build()
.launch();
Three things to note:
- The session token goes into
init(). It is no longer a config field. launch()presents its own modal or new tab, so you do not pass a DOM container. Any argument you pass is ignored.- The instance only exists after
build(); calllaunch()andclose()on it.
4. Handle the result
widthWebSdk
.init(sdkToken)
.withConf({ /* ... */ })
.on('idCheck.onApplicantVerificationCompleted', (payload) => {
// payload.reviewResult.reviewAnswer carries the outcome
})
.on('idCheck.onError', (payload) => {
// payload.code, payload.error
})
.build()
.launch();
// Or take every event through a single handler:
// .onMessage((type, payload) => { ... })
The SDK does not throw. Every error is delivered through
idCheck.onError as { code, error }, and launch() returns an empty
string instead of a session value.
Launch options
Required (inside withConf)
| Parameter | Type | Description |
|---|---|---|
apiKey | string | Your Width public API Key (ak_...) |
domainId | string | Business unit ID |
email | string | Applicant email |
customerId | string | Your internal user ID |
Optional
| Parameter | Type | Default | Description |
|---|---|---|---|
mode | 'modal' | 'newtab' | 'inline' | 'modal' | UI presentation. inline fills the viewport with no overlay and no close button |
language | 'en' | 'zh' | 'en' | UI language. Browser language is no longer auto-detected |
zIndex | number | 9999 | Modal stacking order |
eventType | 'KYC_EVENT' | 'KYB_EVENT' | — | Names the verification type outright; omit to keep the existing routing |
Methods
| Method | Purpose |
|---|---|
widthWebSdk.init(accessToken) | Start configuring with the session token; returns a builder |
.withConf(conf) | Supply the configuration |
.on(eventName, callback) | Subscribe to one event |
.onMessage(callback) | Take every event through one handler |
.build() | Produce the instance |
instance.launch() | Start verification; returns a non-empty session string, or an empty string on failure |
instance.close() | Close the verification window |
WidthWebSDK.openLiveness(options) | Launch one-off liveness |
Events
| Event | Fires when |
|---|---|
idCheck.onApplicantVerificationCompleted | Verification finishes and an outcome is available |
idCheck.onApplicantSubmitted | The applicant has submitted their documents |
idCheck.onApplicantStatusChanged | The applicant's status changes |
idCheck.onError | Something fails. The SDK does not throw; errors come through here |
openLiveness
const livenessToken = new URLSearchParams(window.location.search).get('token');
const { sdkToken, domainId } =
await fetch('/your-backend/sdk-token').then(r => r.json());
WidthWebSDK.openLiveness({
apiKey: 'ak_your_public_api_key',
appToken: sdkToken,
livenessToken,
domainId,
mode: 'modal',
onResult: handleVerificationResult,
});
Verification result
type VerificationResult =
| { type: 'COMPLETED'; executionId: string; decision: 'APPROVED' | 'REJECTED' }
| { type: 'WAITING'; executionId: string }
| { type: 'FAILED'; code: VerificationErrorCode; message: string; executionId?: string }
| { type: 'CANCELLED' };
type VerificationErrorCode = 'INVALID_INPUT' | 'SDK_ERROR' | 'WORKFLOW_FAILED';
| 程式碼 | 含義 | Retryable |
|---|---|---|
INVALID_INPUT | Launch parameter issue | No — fix code |
SDK_ERROR | Network / runtime error | Yes — retry |
WORKFLOW_FAILED | Non-success workflow state | No — contact support |
Browser support
| Platform | Minimum |
|---|---|
| Chrome / Edge | 92+ |
| Firefox | 95+ |
| Safari (desktop & iOS) | 15.4+ |
| Chrome (Android) | 92+ |
The SDK relies on crypto.randomUUID, ES2020 syntax, and modern fetch + postMessage. Older browsers are not supported.
CSP 與許可權策略
Content-Security-Policy: frame-src https://<width-verify-origin>;
Permissions-Policy: camera=(self "https://<width-verify-origin>"),
microphone=(self "https://<width-verify-origin>"),
accelerometer=(self "https://<width-verify-origin>"),
gyroscope=(self "https://<width-verify-origin>")
In the Width Admin Console, add your domain under SSO & Integration → API Keys & Webhooks → Web SDK Settings → Allowed Origins.
Troubleshooting
| Symptom | Cause | Resolution |
|---|---|---|
Verification page shows Invalid apiKey | The installed build and the key belong to different environments | Reinstall for the right environment: default release for production, @beta for platform |
apiKey is required. | Missing apiKey | Pass the public API Key |
accessToken is required. | No session token passed to init() | Check the sdk.credentials integration |
domainId is required. | Missing domainId | Pass the business unit ID returned with the token |
email is required. | Missing email | Both email and customerId are now required |
customerId is required. | Missing customerId | Pass your internal user ID |
email format is invalid. | Invalid format | Validate in your form first |
apiKey / accessToken mismatch | Different apps / tenants | Reissue the token with the same key |
SDK_ERROR | Network issue | Retry launch() |
| Modal won't appear | Domain not allowlisted / bad CSP | Add domain, update CSP |
| Camera denied | Strict Permissions-Policy | Update policy headers |
idCheck.* handler never fires | Session ongoing, or the handler was registered after build() | Wait, or subscribe before calling build() |
If the same key works against the backend API and only the SDK reports
Invalid apiKey, the install is on the wrong environment rather than the
key being at fault. The Web SDK takes the same apiKey that signs the
sdk.credentials request; withConf accepts no second key.