Web SDK 集成指南
概述
Width Web SDK 为 Web
应用提供即插即用的身份验证流程。支持 KYC 和 KYB
工作流,包括证件采集、活体检测与审核。SDK
自托管:你引入一个包,用链式调用配置它,再调用
launch() — SDK
会自行弹出弹窗或打开新标签页,并通过事件回报结果。无需提供 DOM
容器。它以 ESM 构建发布,自带完整 TypeScript 类型定义,无 peer 依赖。
系统要求
| 要求 | 最低版本 |
|---|---|
| 模块格式 | ESM(import 引入) |
| TypeScript | 自带类型定义(可选) |
| Peer 依赖 | 无 |
| 后端 |
需通过 sdk.credentials 签发会话 token
|
步骤 1:获取会话 token(后端)
Web SDK 从一个一次性会话 token(即
sdkToken)开始。你的服务端向 Width 的
sdk.credentials 接口请求它,再把它交给浏览器。公开的
apiKey 与
domainId 在后续步骤中配置;带密钥的签名只在你的服务端完成。
// 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" }
]
}
}
}
在后端开一个小接口(例如
/your-backend/sdk-token),把 sdkToken 和
domainId 返回给前端。把从 domains[] 中选定的
domainId 在步骤 3 传入 SDK 配置。
sdkToken
仅可使用一次。每当用户开始一次新的验证时,都要重新签发一个新 token
— 不要缓存或复用。
sdk.credentials 调用使用与其他 API 相同的网关基址及 HMAC 签名,详见 OpenAPI 参考。步骤 2:安装 SDK
从 npm 安装。安装后即可按包名
@width-com/web-sdk 引入 — ESM 构建,自带 TypeScript
类型定义,无 peer
依赖。装哪个标签同时决定 SDK 连到哪个环境:按本指南基准,默认(latest)版对应正式环境,@beta
标签版对应 platform(测试)环境。请按你的
apiKey
所属环境选择对应标签,并在上线前确认你装的这一版实际打开的是哪个地址(见下方提示)。
# 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
Invalid apiKey
验证页地址是随每个发布版本编译进包里的,装好之后连哪个环境就定死了,withConf
里也没有任何字段能改它。按本指南基准,默认(latest)版打开的是
https://identity.width.com/sdk-page(正式环境),@beta
版打开的是
https://identity-platform.width.com/sdk-page(platform
环境)—
这个对应关系取决于两个标签本指南记录的各自指向哪个构建,是当下的状态、不是固定规则,上线前请自行确认你装的这一版实际打开的是哪个地址。如果标签与你
apiKey 所属的环境对不上,这个页面会直接显示
Invalid apiKey,流程就停在这里 — 用同一把 key
调用后端 OpenAPI 不受影响,所以问题不在 key
本身。按环境选标签,不要按版本号新旧选。前后端共用一把 key:withConf
里与 key 相关的字段只有 apiKey 一个,它就是你签发
sdk.credentials 时用的那一把 — 包内自带的
TypeScript 类型定义列出了它接受的全部字段。供参考:@beta
在本指南基准中指向 1.0.0-beta.3(2026-08-05 发布),默认版为
1.0.1,两者都含 eventType 与
language。
步骤 3:初始化(快速开始)
用一条链式调用构建验证:init 传入会话 token,withConf
传入配置,用 on / onMessage 订阅事件,再
build().launch()。launch()
会弹出自托管的弹窗或新标签页 — 无需提供 DOM 容器。
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 neededeventType 与 language 需要
1.0.1 及以上版本
这两个字段自 1.0.1(2026-08-20 发布,即本指南记录的默认
latest 版)起提供,@beta 在本指南基准中指向的
1.0.0-beta.3
同样含有它们 — 也就是说两个环境都能用,标签仍按环境选(见步骤
2)。1.0.0 及更早的正式版不含它们:TypeScript
项目会编译失败,纯 JavaScript 项目则不报错、也不生效。eventType
只接受 KYC_EVENT 或 KYB_EVENT,language
只接受 en 或
zh;两者都是选填,不传即走默认行为。
配置(withConf)
| 字段 | 类型 | 必填 | 描述 |
|---|---|---|---|
apiKey |
string | 是 |
公开 Width API Key(ak_...)
|
domainId |
string | 是 |
业务域(domain)GID,取自 sdk.credentials 的
domains[]
|
email |
string | 是 | 终端用户邮箱 |
customerId |
string | 是 | 你的内部用户 ID |
mode |
'modal' | 'newtab' |
否 |
展示形态,默认 'modal'
|
language |
'en' | 'zh' |
否 |
界面语言。不传该字段时界面为英文;不会按浏览器语言自动判断。自
1.0.1 起提供。
|
eventType |
'KYC_EVENT' | 'KYB_EVENT' |
否 |
这次要跑哪一类流程——个人核验(KYC_EVENT)还是企业核验(KYB_EVENT)。不传该字段时按默认方式路由(见工作流与决策模型)。自 1.0.1 起提供。
|
zIndex |
number | 否 | 本指南基准未指定默认值。弹窗层级 |
步骤 4:处理验证结果
launch() 成功时返回本次会话的完整核验页 URL,失败时返回空字符串。错误通过 idCheck.onError 回传。
按你订阅的事件分支处理。最终结论由
reviewResult.reviewAnswer 携带,只有两个取值:结论为通过时是
GREEN,其余一律是 RED。
.on('idCheck.onApplicantVerificationCompleted', (payload) => {
const { reviewResult, executionId } = payload;
if (reviewResult.reviewAnswer === 'GREEN') {
grantAccess(executionId);
} else {
handleNotApproved(executionId);
}
})每个事件对应一条处理分支:
| 事件 | 处理方式 |
|---|---|
idCheck.onApplicantVerificationCompleted |
已有最终结论 — 读取 reviewAnswer
|
idCheck.onApplicantSubmitted |
已提交;进入异步审核 |
idCheck.onError |
上报失败 |
width.onCancelled (via onMessage) |
用户关闭了流程 |
executionId 是与后端 / OpenAPI 对账的句柄。对
idCheck.onApplicantSubmitted(异步审核)场景请持久化它,以便服务端稍后轮询最终状态。
验证结果参考
最终结论由 reviewResult.reviewAnswer 携带:
reviewAnswer |
含义 |
|---|---|
GREEN |
通过 |
RED |
未通过。SDK 会把「通过」以外的所有结论都映射成
RED——被拒绝的会落在这里,其他非通过的结果同样落在这里。把
RED 当作「已拒绝」处理之前,先查案件状态(workflow.execution.status
返回的 caseStatus,或到控制台看该案件)。
|
1.0.1(1.0.0-beta.3 相同),Web SDK
只把结论收成这两个值,没有第三个取值。GREEN
走通过分支;RED
按「未通过」处理,除非案件状态确认它就是拒绝。
方法
| 调用 | 返回 | 作用 |
|---|---|---|
widthWebSdk.init(accessToken) |
builder | 用会话 token 开始链式配置 |
.withConf(conf) |
builder | 提供配置(见「配置」) |
.on(eventName, callback) |
builder | 订阅单个事件 |
.onMessage((type, payload) => {}) |
builder | 用一个处理器接收所有事件 |
.build() |
实例 | 完成构建 |
instance.launch(container?) |
string |
启动验证,返回本次会话的整条验证页网址 — 形如
https://identity.width.com/sdk-page?sdkMode=1&sdkSession=<会话 id>&apiKey=<你的 apiKey>&theme=light,其中主机名取自你安装的那一版(见步骤 2)—
启动失败时返回 ''。这条网址对应的就是本次验证会话,它背后的会话
token 仅一次性有效(见步骤 1)。container
被忽略
|
instance.close() |
void |
主动关闭验证窗口 |
事件参考
用 .on(eventName, callback)
注册单个事件,或用
.onMessage((type, payload) => {}) 接收全部。
| 事件 | 触发时机 | 载荷 |
|---|---|---|
idCheck.onApplicantVerificationCompleted |
流程完成、有结论 |
{ reviewStatus: 'completed', reviewResult: { reviewAnswer:
'GREEN' | 'RED' }, executionId }
|
idCheck.onApplicantStatusChanged |
与 Completed(idCheck.onApplicantVerificationCompleted)同时触发 | { reviewStatus: 'completed', reviewResult: { reviewAnswer: 'GREEN' | 'RED' }, executionId } |
idCheck.onApplicantSubmitted |
已提交、异步审核中 | { executionId } |
idCheck.onError |
失败 | { code, error, executionId? } |
width.onCancelled |
用户关闭(仅经 onMessage)
|
{} |
idCheck.onError 投递 —
SDK 不会向你的代码抛异常。
错误码
在 idCheck.onError 中,payload.code
为以下之一:
| 码 | 含义 | 可重试 |
|---|---|---|
INVALID_INPUT |
启动参数缺失或非法 | 否 |
SDK_ERROR |
网络、超时或内部错误 | 是 |
WORKFLOW_FAILED |
流程以非成功状态结束 | 否 |
浏览器支持
Web SDK 依赖 crypto.randomUUID、ES2020 语法,以及现代的
fetch 与
postMessage。下表是同时具备这三项的最低版本,低于该版本的浏览器不在支持范围内。
| 浏览器 | 最低版本 |
|---|---|
| Chrome / Edge(桌面端) | 92+ |
| Firefox | 95+ |
| Safari(macOS 与 iOS) | 15.4+ |
| Chrome(Android) | 92+ |
故障排查
| 现象 | 可能原因 | 解决 |
|---|---|---|
onError { INVALID_INPUT, "apiKey is required." }
|
apiKey 缺失或为空
|
传入公开 API Key |
onError { INVALID_INPUT, "accessToken is required." }
|
后端未返回 sdkToken
|
检查 sdk.credentials 集成
|
onError { INVALID_INPUT, "domainId is required." }
|
初始化时 withConf 未传 domainId
|
传入你从 sdk.credentials 返回的
domains[] 中选定那个 domain 的
id
|
onError { INVALID_INPUT, "email is required." }
|
缺少 email
|
传入终端用户邮箱 |
onError { INVALID_INPUT, "customerId is required." }
|
缺少 customerId
|
传入内部用户 ID |
onError { INVALID_INPUT, "email format is invalid." }
|
邮箱格式错误 | 表单先校验 |
onError { INVALID_INPUT, "apiKey does not match SDK credentials."
}
|
apiKey 与
accessToken 不属于同一应用
|
用同一 apiKey 重新签发 token
|
onError { SDK_ERROR } |
网络 / 连接问题 |
重新构建链并再次 launch()
|
| 弹窗不出现 / iframe 被拦截 |
域名未加白名单,或 CSP frame-src 缺少该源
|
在控制台 API & Integration
下把该源加进白名单(见表格下方说明),并在 CSP
frame-src 里放行它
|
| 相机权限被拒 |
宿主 Permissions-Policy 过严
|
更新你的策略头(见下方说明) |
| 无事件触发 | 会话仍在进行,或未注册回调 |
等待完成;检查 .on() 回调
|
https:// 或 http://
开头、不带路径、也不支持通配符,因此每个主机名都要单独写一条(例如
https://app.example.com 和
https://checkout.example.com)。如果这个页面上看不到生成密钥的按钮,通常是本指南记录的账号的角色缺少管理
API 密钥这条权限 —
这类按钮按权限渲染,没有权限时是直接不显示、而不是变灰,请让租户管理员为你开通。
modal 模式下,SDK 会在你自己的页面里插入一个 iframe
来加载验证页,所以只要你的页面设了 Content-Security-Policy,就必须在
frame-src 里放行这个源,并通过
Permissions-Policy
把相机(以及活体所需的麦克风 /
运动传感器)授权给同一个源。填哪个源取决于你装的是哪个标签(见步骤
2):默认(latest,本指南记录的为 1.0.1)版加载的是
https://identity.width.com,@beta
版加载的是
https://identity-platform.width.com。按你实际上线的那一版选对应的一段
— 源没写进 frame-src,浏览器就会拦掉这个
iframe,验证窗口只剩空白。
# 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")