sumsub-integrate-id-connect

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Sumsub — ID Connect integration

Sumsub — ID Connect 集成

Embed the "Verify with Sumsub ID" OIDC flow end-to-end — preconditions → preflight → button → backend exchange → consent → applicant in your workspace.
端到端嵌入「通过Sumsub ID验证」OIDC流程——前置条件 → 预检 → 按钮 → 后端交换 → 同意 → 申请人信息落地到你的工作区。

⚠️ Sandbox tokens only

⚠️ 仅使用沙箱令牌

Do not use a production App Token / secret while wiring this up. Insist on a sandbox pair from https://cockpit.sumsub.com/checkus/devSpace/appTokens — toggle the workspace to Sandbox first, then Create. Token + secret are revealed once at creation; copy both before closing the dialog. The preflight enforces this with an
sbx:
prefix check.
Deeper auth mechanics:
sumsub-api-auth
.
在配置过程中请勿使用生产环境的App Token/密钥。请从https://cockpit.sumsub.com/checkus/devSpace/appTokens获取沙箱令牌对——先将工作区切换为Sandbox,再点击Create。令牌和密钥仅在创建时显示一次;请在关闭对话框前复制两者。预检流程会通过
sbx:
前缀检查强制验证这一点。
更深入的认证机制:
sumsub-api-auth

⛔ Order of operations — do NOT scaffold an app first

⛔ 操作顺序——请勿先搭建应用

This is the single most important rule of this skill. Write no frontend or backend code, create no demo project, scaffold nothing until BOTH gates below are green. The client-side Sumsub setup (Stage 0a) is manual and cannot be done by you — jumping to code before it exists produces an app that can't authenticate, can't redirect, and can't reuse. Always follow this sequence:
  1. Confirm Stage 0a preconditions with the user — one by one. Do not assume any item is done. For each, ask the user whether it exists; if it doesn't, stop and hand them the exact manual step and where to do it (the support email template, the Dev Space → OIDC Settings registration, the App Token minting page, the level creation hand-off). These are Sumsub-dashboard / support actions only — there is no public API for them, so you cannot do them for the user. Wait until every item is confirmed before moving on.
  2. Run Stage 0b preflight and require exit 0. Only after the preconditions are confirmed, run
    scripts/preflight.sh
    . Resolve every FAIL (loop back to Stage 0a as needed). Do not proceed on any FAIL.
  3. Only now build. With both gates green, proceed to Stage 1 and scaffold the frontend/backend.
If the user says "just build the demo" before the gates are green, briefly explain why it won't work yet and walk them through the missing precondition first. The code is the last step, not the first.
这是本方案最重要的规则。在以下两个检查项全部通过前,请勿编写任何前端或后端代码、创建演示项目、搭建任何框架。客户端侧的Sumsub设置(Stage 0a)为手动操作,无法自动完成——跳过该步骤直接编写代码会导致应用无法认证、无法重定向、无法复用信息。请严格遵循以下顺序:
  1. 逐一与用户确认Stage 0a的前置条件。请勿假设任何项已完成。对于每一项,询问用户是否已完成;若未完成,停止操作并告知用户具体的手动步骤及操作位置(支持邮件模板、Dev Space → OIDC Settings注册页面、App Token生成页面、验证等级创建流程)。这些操作仅能通过Sumsub控制台/支持团队完成——无公开API可用,因此你无法代用户完成。等待所有项确认完成后再进行下一步。
  2. 运行Stage 0b预检流程并要求返回值为0。仅在前置条件全部确认后,运行
    scripts/preflight.sh
    。解决所有FAIL项(必要时回到Stage 0a)。若存在任何FAIL项,请勿继续。
  3. 仅在此时开始构建。在两个检查项全部通过后,再进入Stage 1并搭建前端/后端。
如果用户在检查项通过前要求「直接构建演示项目」,请简要解释为何无法正常工作,并先引导他们完成缺失的前置条件。代码编写是最后一步,而非第一步。

What ID Connect is (and isn't)

ID Connect是什么(以及不是什么)

Sumsub ID Connect lets a business request specific verified identity claims — age, name, nationality, email, country of residence, humanness — from a Sumsub ID account belonging to the end-user. Sumsub hosts the entire user-facing flow: sign-in, account creation (if needed), email OTP, any missing verification steps, and a per-recipient consent screen. Your app receives the result as a signed OIDC token plus, optionally, an applicant materialised in your workspace through Reusable KYC.
This is not the WebSDK. The WebSDK runs a full KYC capture inside your page; ID Connect delegates the whole UX to Sumsub.
Sumsub ID Connect允许企业向终端用户的Sumsub ID账户请求特定的已验证身份信息——年龄、姓名、国籍、邮箱、居住国、真人验证状态。Sumsub托管整个面向用户的流程:登录、账户创建(如需)、邮箱OTP、任何缺失的验证步骤,以及针对接收方的同意界面。你的应用将收到签名后的OIDC令牌,还可选择通过可复用KYC将申请人信息落地到你的工作区。
不是WebSDK。WebSDK在你的页面内运行完整的KYC采集流程;而ID Connect将整个用户体验委托给Sumsub。

The lifecycle in one picture

生命周期概览

 ┌──────────────────────────────┐
 │ Stage 0a Preconditions check │  manual setup on Sumsub side
 │ Stage 0b scripts/preflight.sh │  automated validation
 └──────────────┬───────────────┘
                │ clientId, levelName, allowlist
 ┌──────────────▼───────────────┐
 │ Stage 1: Frontend "Verify…"  │  authorize redirect with PKCE
 │   button                      │
 └──────────────┬───────────────┘
                │ onSuccess({ code, state })
 ┌──────────────▼───────────────┐
 │ Stage 2: code → access_token  │  POST id.sumsub.com/api/snsId/oauth/token
 │   (client_secret_basic)       │
 └──────────────┬───────────────┘
                │ access_token
 ┌──────────────▼───────────────┐
 │ Stage 3a: → share token       │  POST /resources/accessTokens/sumsubIdShareToken
 └──────────────┬───────────────┘
                │ sharingAllowed?
        ┌───────┴────────┐
        │ false           │
        ▼                ▼ true
 ┌──────────────────┐ ┌──────────────────────────────┐
 │ Stage 3b:        │ │ Stage 4: reuse/preview +     │
 │ shareLink        │ │   reuse/commit               │
 │ redirect + retry │ │ → applicant in your workspace │
 └────────┬─────────┘ └──────────────────────────────┘
          └──────► back to Stage 3a (retry — now sharingAllowed: true)
 ┌──────────────────────────────┐
 │ Stage 0a Preconditions check │  Sumsub侧的手动设置
 │ Stage 0b scripts/preflight.sh │  自动化验证
 └──────────────┬───────────────┘
                │ clientId, levelName, allowlist
 ┌──────────────▼───────────────┐
 │ Stage 1: Frontend "Verify…"  │  带PKCE的授权重定向
 │   button                      │
 └──────────────┬───────────────┘
                │ onSuccess({ code, state })
 ┌──────────────▼───────────────┐
 │ Stage 2: code → access_token  │  POST id.sumsub.com/api/snsId/oauth/token
 │   (client_secret_basic)       │
 └──────────────┬───────────────┘
                │ access_token
 ┌──────────────▼───────────────┐
 │ Stage 3a: → share token       │  POST /resources/accessTokens/sumsubIdShareToken
 └──────────────┬───────────────┘
                │ sharingAllowed?
        ┌───────┴────────┐
        │ false           │
        ▼                ▼ true
 ┌──────────────────┐ ┌──────────────────────────────┐
 │ Stage 3b:        │ │ Stage 4: reuse/preview +     │
 │ shareLink        │ │   reuse/commit               │
 │ redirect + retry │ │ → 申请人信息落地到你的工作区 │
 └────────┬─────────┘ └──────────────────────────────┘
          └──────► 返回Stage 3a(重试——此时sharingAllowed: true)

Endpoints

端点

METHODURLAuthStage
(browser redirect)
https://id.sumsub.com/api/snsId/oauth/authorize
none1
GET
https://id.sumsub.com/.well-known/openid-configuration
none0b
GET
https://id.sumsub.com/.well-known/jwks.json
none(id_token validation)
POST
https://id.sumsub.com/api/snsId/oauth/token
client_secret_basic
2
POST
https://api.sumsub.com/resources/accessTokens/sumsubIdShareToken
App Token + HMAC3a
POST
https://api.sumsub.com/resources/snsId/oauth/shareLink
App Token + HMAC + Bearer3b
GET
https://api.sumsub.com/resources/api/reusableIdentity/reuse/preview
App Token + HMAC4
POST
https://api.sumsub.com/resources/api/reusableIdentity/reuse
App Token + HMAC4
The "Enable Sumsub ID Connect" toggle, OIDC client registration (client_secret + redirect_uri allowlist), and sharing-partner registration are Sumsub dashboard UI only — no public-API equivalent.
METHODURLAuthStage
(浏览器重定向)
https://id.sumsub.com/api/snsId/oauth/authorize
none1
GET
https://id.sumsub.com/.well-known/openid-configuration
none0b
GET
https://id.sumsub.com/.well-known/jwks.json
none(id_token验证)
POST
https://id.sumsub.com/api/snsId/oauth/token
client_secret_basic
2
POST
https://api.sumsub.com/resources/accessTokens/sumsubIdShareToken
App Token + HMAC3a
POST
https://api.sumsub.com/resources/snsId/oauth/shareLink
App Token + HMAC + Bearer3b
GET
https://api.sumsub.com/resources/api/reusableIdentity/reuse/preview
App Token + HMAC4
POST
https://api.sumsub.com/resources/api/reusableIdentity/reuse
App Token + HMAC4
「启用Sumsub ID Connect」开关、OIDC客户端注册(client_secret + redirect_uri白名单)、共享合作伙伴注册均仅能通过Sumsub控制台UI操作——无公开API等效操作。

Stage 0a — Preconditions checklist

Stage 0a — 前置条件清单

Before any code or preflight, the following must already exist on the Sumsub side. Each item maps to a check below or to a runtime failure if missed.
  • Sumsub support has enabled ID Connect for your workspace. This is not a self-serve toggle — until support flips it, no OIDC client can be created and the preflight will report
    connect_token: ID Connect not enabled for this workspace
    . Email your CSM or
    support@sumsub.com
    first and wait for confirmation. Template:
    ```
    Subject: Enable Sumsub ID Connect for clientId <YOUR_CLIENT_ID> (sandbox)
    
    Hi Sumsub team,
    
    Please enable Sumsub ID Connect for our workspace.
    
      Environment:         sandbox
      clientId:            <YOUR_CLIENT_ID>          (Dashboard top-left)
      Intended scopes:     openid, share, name        (adjust as needed)
      Intended recipients: self                       (or list partner clientIds)
      Use case:            <one-line description of where the button will live>
    
    Once enabled we'll register the OIDC client + redirect URIs in
    Dev Space → OIDC Settings.
    
    Thanks
    ```
  • App Token + secret (sandbox). Minted at https://cockpit.sumsub.com/checkus/devSpace/appTokens with workspace toggled to Sandbox. Both values shown once at creation. → exported as
    SUMSUB_APP_TOKEN
    /
    SUMSUB_SECRET_KEY
    .
  • OIDC client registered at Dashboard → Dev Space → OIDC Settings. Capture all three: -
    client_id
    (public — goes in the browser, used as
    forClientId
    for the share token in the common self-recipient case) -
    client_secret
    (shown ONCE at creation; if you lose it you must regenerate, which invalidates existing integrations). Required for the Stage 2 code→token exchange — keep it server-side only. -
    redirect_uri
    allowlist
    populated for BOTH callbacks (Sumsub matches byte-for-byte — trailing slash, casing, port, query all matter): - your authorize callback (e.g.
    https://yourapp.com/auth/callback
    ) - your shareLink callback (e.g.
    https://yourapp.com/share-complete
    ) Sumsub redirects the browser back to these URLs and requires public HTTPS — a bare
    http://localhost:3000
    won't work for local testing. To develop on your machine, expose your local server with a public HTTPS tunnel (ngrok) and allowlist that origin — see Local development with ngrok below.
  • At least one verification level exists in this workspace. Capture
    levelName
    . If none, hand off to
    sumsub-create-level
    first — Stage 4 can't materialise an applicant without one.
  • At least one Sumsub ID account with verified data for end-to-end testing. An email-only account (no completed KYC) makes Stage 4 fail with errorCode
    10521 reusable-kyc-inactive-sumsub-id-account
    — see the error table at the end of Stage 4.
在编写任何代码或执行预检前,Sumsub侧必须已完成以下操作。每一项对应下方的检查项,若遗漏会导致运行时失败。
  • Sumsub支持团队已为你的工作区启用ID Connect。这不是自助开关——在支持团队启用前,无法创建OIDC客户端,预检流程会报告
    connect_token: ID Connect not enabled for this workspace
    。请先联系你的客户成功经理或发送邮件至
    support@sumsub.com
    ,等待确认。邮件模板:
    ```
    Subject: Enable Sumsub ID Connect for clientId <YOUR_CLIENT_ID> (sandbox)
    
    Hi Sumsub team,
    
    Please enable Sumsub ID Connect for our workspace.
    
      Environment:         sandbox
      clientId:            <YOUR_CLIENT_ID>          (控制台左上角)
      Intended scopes:     openid, share, name        (按需调整)
      Intended recipients: self                       (或列出合作伙伴clientIds)
      Use case:            <按钮部署场景的一行描述>
    
    Once enabled we'll register the OIDC client + redirect URIs in
    Dev Space → OIDC Settings.
    
    Thanks
    ```
  • App Token + 密钥(沙箱环境)。在https://cockpit.sumsub.com/checkus/devSpace/appTokens生成,需先将工作区切换为Sandbox。两者仅在创建时显示一次。→ 导出为
    SUMSUB_APP_TOKEN
    /
    SUMSUB_SECRET_KEY
  • 已注册OIDC客户端,路径为控制台 → Dev Space → OIDC Settings。记录以下三项: -
    client_id
    (公开——可在浏览器中使用,在常见的自接收方场景中用作共享令牌的
    forClientId
    ) -
    client_secret
    (仅在创建时显示一次;若丢失必须重新生成,这会使现有集成失效)。Stage 2的码转令牌交换需要此字段——请仅在服务器侧保存。 -
    redirect_uri
    白名单
    ,需填入两个回调地址(Sumsub会进行字节级匹配——末尾斜杠、大小写、端口、查询参数均会影响匹配): - 你的授权回调地址(例如
    https://yourapp.com/auth/callback
    ) - 你的shareLink回调地址(例如
    https://yourapp.com/share-complete
    ) Sumsub会将浏览器重定向回这些URL,且要求地址为公开HTTPS——本地测试使用的
    http://localhost:3000
    无法正常工作。如需在本地开发,请使用公开HTTPS隧道(如ngrok)暴露本地服务器,并将该域名加入白名单——详见下方的使用ngrok进行本地开发
  • 工作区中至少存在一个验证等级。记录
    levelName
    。若不存在,请先使用
    sumsub-create-level
    创建——Stage 4无法在无验证等级的情况下落地申请人信息。
  • 至少存在一个带有已验证数据的Sumsub ID账户,用于端到端测试。仅含邮箱的账户(未完成KYC)会导致Stage 4失败,错误码为
    10521 reusable-kyc-inactive-sumsub-id-account
    ——详见Stage 4末尾的错误表。

Stage 0b — Preflight

Stage 0b — 预检流程

scripts/preflight.sh
machine-validates the preconditions that can be probed without minting OIDC codes or creating applicants. It does not mutate anything.
bash
SUMSUB_APP_TOKEN=sbx:... SUMSUB_SECRET_KEY=... \
  bash scripts/preflight.sh
Checks (each independent — one failing doesn't abort the others):
CheckWhat it proves
connect_token
App-Token HMAC signing is accepted and ID Connect is enabled for the workspace — probes the endpoint with a known-bad code and expects
401 Invalid code
(a
404 invalid clientId
means ID Connect isn't enabled). This is a connectivity/entitlement probe, not the Stage 2 exchange route.
oidc_discovery
https://id.sumsub.com/.well-known/openid-configuration
returns a valid OIDC discovery doc
levels
At least one verification level exists in this workspace
Exit code:
0
on all-PASS / PASS+WARN,
10
on any FAIL. Resolve all FAILs before Stage 1.
scripts/preflight.sh
会自动验证无需生成OIDC码或创建申请人即可检测的前置条件。该脚本不会修改任何数据
bash
SUMSUB_APP_TOKEN=sbx:... SUMSUB_SECRET_KEY=... \
  bash scripts/preflight.sh
检查项(彼此独立——一项失败不会终止其他检查):
检查项验证内容
connect_token
App-Token的HMAC签名被接受,且工作区已启用ID Connect——向端点发送已知无效的代码,预期返回
401 Invalid code
(若返回
404 invalid clientId
则表示ID Connect未启用)。这是连通性/权限检测,而非Stage 2的交换路由。
oidc_discovery
https://id.sumsub.com/.well-known/openid-configuration
返回有效的OIDC发现文档
levels
工作区中至少存在一个验证等级
退出码:所有项通过/通过+警告时返回
0
,存在任何失败项时返回
10
。在进入Stage 1前解决所有失败项。

Local development with ngrok

使用ngrok进行本地开发

ID Connect can't be tested against
http://localhost
. Sumsub redirects the browser back to your
redirect_uri
after the authorize flow (Stage 1) and after the shareLink consent flow (Stage 3b), and it requires the URL to be public HTTPS and present in the OIDC client's
redirect_uri
allowlist byte-for-byte. A bare
localhost
URL is neither public nor HTTPS, so the redirect fails. For local development, put a public HTTPS tunnel in front of your local server with ngrok and allowlist that origin.
  1. Start your backend locally (the example listens on
    PORT=3000
    ):
    bash
    PORT=3000 node examples/express-callback.js
  2. Open a tunnel to that port. Prefer a reserved (static) domain so the URL survives restarts — a free random ngrok URL changes on every launch and forces you to re-edit the allowlist each time:
    bash
    ngrok http 3000 --domain=your-reserved-name.ngrok-free.app
    # or, ephemeral URL (changes each restart): ngrok http 3000
    ngrok prints a forwarding URL, e.g.
    https://your-reserved-name.ngrok-free.app
    .
  3. Allowlist BOTH callbacks at the ngrok origin in Dashboard → Dev Space → OIDC Settings (sandbox workspace) — byte-for-byte, including trailing slash:
    https://your-reserved-name.ngrok-free.app/
    https://your-reserved-name.ngrok-free.app/share-complete
  4. Point the app at the public URL, not localhost. The browser's
    redirect_uri
    and the server-side shareLink
    redirectUri
    must derive from the same public origin, so set
    PUBLIC_BASE_URL
    and restart:
    bash
    PUBLIC_BASE_URL=https://your-reserved-name.ngrok-free.app \
      PORT=3000 node examples/express-callback.js
    Then open the ngrok URL (not
    localhost:3000
    ) in the browser so the
    redirect_uri
    it sends matches the allowlist.
⚠️ Re-register on every URL change. If you use an ephemeral ngrok URL, the allowlist entry (and
PUBLIC_BASE_URL
) must be updated each time ngrok restarts. A reserved domain avoids this churn.
ngrok's free interstitial ("You are about to visit…") only affects API/XHR calls, not top-level browser navigation, so it doesn't block the OIDC redirects. A reserved domain or paid plan removes it entirely.
ID Connect无法针对
http://localhost
进行测试。Sumsub在授权流程(Stage 1)和shareLink同意流程(Stage 3b)后会将浏览器重定向回你的
redirect_uri
,且要求URL为公开HTTPS并存在于OIDC客户端的
redirect_uri
白名单中(字节级匹配)。纯
localhost
地址既非公开地址也非HTTPS,因此重定向会失败。如需本地开发,请使用ngrok在本地服务器前搭建公开HTTPS隧道,并将该域名加入白名单。
  1. 在本地启动后端服务(示例监听
    PORT=3000
    ):
    bash
    PORT=3000 node examples/express-callback.js
  2. 为该端口开启隧道。建议使用保留(静态)域名,以便URL在重启后保持不变——免费的随机ngrok URL每次启动都会变化,会迫使你每次都重新编辑白名单:
    bash
    ngrok http 3000 --domain=your-reserved-name.ngrok-free.app
    # 或使用临时URL(每次重启变化):ngrok http 3000
    ngrok会打印转发URL,例如
    https://your-reserved-name.ngrok-free.app
  3. 在控制台 → Dev Space → OIDC Settings(沙箱工作区)中将两个回调地址加入白名单——字节级匹配,包括末尾斜杠:
    https://your-reserved-name.ngrok-free.app/
    https://your-reserved-name.ngrok-free.app/share-complete
  4. 将应用指向公开URL,而非localhost。浏览器的
    redirect_uri
    和服务器侧的shareLink
    redirectUri
    必须来自同一个公开域名,因此请设置
    PUBLIC_BASE_URL
    并重启服务:
    bash
    PUBLIC_BASE_URL=https://your-reserved-name.ngrok-free.app \
      PORT=3000 node examples/express-callback.js
    然后在浏览器中打开ngrok URL(而非
    localhost:3000
    ),确保发送的
    redirect_uri
    与白名单匹配。
⚠️ 每次URL变化都需重新注册。若使用临时ngrok URL,每次ngrok重启后都必须更新白名单条目(以及
PUBLIC_BASE_URL
)。使用保留域名可避免此类重复操作。
ngrok的免费 interstitial页面("You are about to visit…")仅影响API/XHR调用,不影响顶级浏览器导航,因此不会阻止OIDC重定向。使用保留域名或付费计划可完全移除该页面。

Stage 1 — Frontend: the button

Stage 1 — 前端:按钮实现

Gate. Do not start this stage until Stage 0a preconditions are all confirmed with the user AND Stage 0b preflight exits 0. If you haven't done both, go back — see "Order of operations" above. This is the first stage where you write code; everything before it is setup you must verify first.
检查项。在与用户确认Stage 0a的前置条件全部完成Stage 0b预检流程返回0前,请勿开始本阶段。若未完成这两项,请返回——详见上方的「操作顺序」。这是第一个需要编写代码的阶段;此前的所有步骤均为需先验证的设置项。

Architecture — what's yours vs Sumsub's

架构——你的部分 vs Sumsub的部分

Sumsub provides: the OIDC service (
id.sumsub.com
), the HMAC API (
api.sumsub.com
), and the optional
@sumsub/id-connect
JS library that mounts the button.
You write both halves of the integration:
  • Frontend (this stage) — page with the button. Generates PKCE, kicks off the OIDC redirect to
    id.sumsub.com
    , receives
    ?code=
    in the callback, POSTs
    { code, codeVerifier }
    to your own backend.
  • Backend (Stages 2-4) — HTTP endpoint that your frontend POSTs to. Signs Sumsub API calls with your App Token + secret (which must NEVER reach the browser), orchestrates the chain
    code → access_token → share token → reuse
    , and returns the materialised applicant.
The two halves are coupled by one route name —
POST /api/sumsub/id-connect/exchange
in the examples; rename to fit your routing. Reference pair (matched):
examples/oidc-button.html
(frontend) +
examples/express-callback.js
(backend). The backend is a thin orchestrator (~180 lines of Node/Express); port it to your stack of choice — the wire format is identical regardless of language.
⚠️
onSuccess
is not a verification signal.
It only confirms the user finished the OIDC consent step and Sumsub issued an authorization code. The actual verification verdict comes from Stage 4 —
applicant.review.reviewStatus
in the
/reuse
response (immediate, if your recipient level runs no additional checks) or the
applicantReviewed
webhook (authoritative, for any level that runs post-reuse checks). Never grant access or unlock features based on
onSuccess
firing.
Three variants — use Method 1 (
createButton
)
by default; the others exist for design-system or no-bundler scenarios.
Sumsub提供:OIDC服务(
id.sumsub.com
)、HMAC API(
api.sumsub.com
),以及可选的
@sumsub/id-connect
JS库用于挂载按钮。
你需要编写集成的两端
  • 前端(本阶段)——包含按钮的页面。生成PKCE,触发向
    id.sumsub.com
    的OIDC重定向,在回调中接收
    ?code=
    ,将
    { code, codeVerifier }
    POST到你自己的后端。
  • 后端(Stages 2-4)——前端POST请求的HTTP端点。使用你的App Token + 密钥对Sumsub API调用进行签名(绝不能让该密钥到达浏览器),协调
    code → access_token → 共享令牌 → 复用
    的流程,并返回落地后的申请人信息。
两端通过一个路由名称关联——示例中为
POST /api/sumsub/id-connect/exchange
;可根据你的路由规则重命名。配套参考示例:
examples/oidc-button.html
(前端) +
examples/express-callback.js
(后端)。后端是一个轻量的协调器(约180行Node/Express代码);可将其移植到你使用的技术栈——无论使用何种语言,通信格式均相同。
⚠️
onSuccess
并非验证信号
。它仅确认用户完成了OIDC同意步骤,且Sumsub已颁发授权码。实际的验证结果来自Stage 4——
/reuse
响应中的
applicant.review.reviewStatus
(若你的接收方等级无需额外检查则会立即返回)或
applicantReviewed
webhook(权威结果,适用于任何复用后需检查的等级)。切勿基于
onSuccess
触发就授予权限或解锁功能
三种实现方式——默认使用方法1(
createButton
;其他方式适用于设计系统限制或无打包工具的场景。

Method 1 — pre-built button via
@sumsub/id-connect
(recommended)

方法1 — 通过
@sumsub/id-connect
使用预构建按钮(推荐)

js
import { createButton } from '@sumsub/id-connect';

// PKCE helpers (S256 — full versions in examples/oidc-button.html).
const b64url = (buf) => btoa(String.fromCharCode(...new Uint8Array(buf)))
  .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
const sha256 = (s) => crypto.subtle.digest('SHA-256', new TextEncoder().encode(s));

// Generate the PKCE pair yourself, store the verifier under YOUR key — Stage 2
// on the backend needs that verifier to exchange the code for an access token.
const verifier  = b64url(crypto.getRandomValues(new Uint8Array(32)));
const challenge = b64url(await sha256(verifier));
sessionStorage.setItem('myapp.pkce.verifier', verifier);

createButton({
  clientId:      '<your-client-id>',
  permissions:   ['openid', 'share', 'name'],
  container:     document.getElementById('button-container'),
  codeChallenge: challenge,                            // library uses YOUR challenge — skips its own PKCE gen
  loginHint:     'user@example.com',                   // optional, prefills the email field
  onSuccess: async ({ code, state }) => {
    const v = sessionStorage.getItem('myapp.pkce.verifier');
    await fetch('/api/sumsub/id-connect/exchange', {
      method:  'POST',
      headers: { 'Content-Type': 'application/json' },
      body:    JSON.stringify({ code, codeVerifier: v, redirectUri: location.href }),
    });
  },
  onError: (err) => console.error('sumsub-id-connect error', err),
});
Why generate PKCE yourself: the Stage 2 token exchange needs the
verifier
. If you let the library generate it, you depend on the library's internal
sessionStorage
key (undocumented per minor version) to read it back in
onSuccess
. Passing your own
codeChallenge
keeps the verifier under a key you own. Same pattern works for Methods 2 and 3.
Runnable example:
examples/oidc-button.html
.
js
import { createButton } from '@sumsub/id-connect';

// PKCE工具函数(S256——完整版本见examples/oidc-button.html)。
const b64url = (buf) => btoa(String.fromCharCode(...new Uint8Array(buf)))
  .replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
const sha256 = (s) => crypto.subtle.digest('SHA-256', new TextEncoder().encode(s));

// 自行生成PKCE对,将verifier存储在你自己的键下——后端的Stage 2需要该verifier来交换授权码获取access token。
const verifier  = b64url(crypto.getRandomValues(new Uint8Array(32)));
const challenge = b64url(await sha256(verifier));
sessionStorage.setItem('myapp.pkce.verifier', verifier);

createButton({
  clientId:      '<your-client-id>',
  permissions:   ['openid', 'share', 'name'],
  container:     document.getElementById('button-container'),
  codeChallenge: challenge,                            // 库使用你提供的challenge——跳过自身的PKCE生成
  loginHint:     'user@example.com',                   // 可选,预填充邮箱字段
  onSuccess: async ({ code, state }) => {
    const v = sessionStorage.getItem('myapp.pkce.verifier');
    await fetch('/api/sumsub/id-connect/exchange', {
      method:  'POST',
      headers: { 'Content-Type': 'application/json' },
      body:    JSON.stringify({ code, codeVerifier: v, redirectUri: location.href }),
    });
  },
  onError: (err) => console.error('sumsub-id-connect error', err),
});
自行生成PKCE的原因:Stage 2的令牌交换需要
verifier
。若让库自行生成,你需依赖库内部的
sessionStorage
键(每个小版本可能未公开)才能在
onSuccess
中读取它。传入你自己的
codeChallenge
可让verifier存储在你自己控制的键下。该模式同样适用于方法2和方法3。
可运行示例:
examples/oidc-button.html

Method 2 — your trigger + library modal

方法2 — 自定义触发元素 + 库弹窗

Same library, you own the trigger element; library opens the flow in an overlay via
openModal({...})
. Use when your design system has its own buttons.
Runnable example:
examples/oidc-modal.html
.
使用同一个库,但你自行控制触发元素;库通过
openModal({...})
在浮层中打开流程。适用于你的设计系统有自定义按钮的场景。
可运行示例:
examples/oidc-modal.html

Method 3 — raw OIDC redirect (no library)

方法3 — 原生OIDC重定向(无库依赖)

Direct navigation to the OIDC authorize endpoint, no Sumsub script on the page. Reach for this when CSP forbids third-party scripts, you're in an SSR-only environment, or you maintain a generic OIDC client across multiple providers.
GET https://id.sumsub.com/api/snsId/oauth/authorize
  ?client_id=<your-client-id>
  &response_type=code
  &redirect_uri=<allowlisted URI>
  &scope=openid+share+name
  &state=<random CSRF nonce>
  &code_challenge=<base64url(SHA256(code_verifier))>
  &code_challenge_method=S256
  &login_hint=<optional email>
You generate the
code_verifier
(43–128 chars, URL-safe), derive
code_challenge = base64url(SHA256(verifier))
, store the verifier (
sessionStorage
), and forward it to your backend after the callback. Sumsub only accepts
code_challenge_method=S256
(
plain
is rejected) and the
redirect_uri
must match the allowlist byte-for-byte.
Runnable example:
examples/oidc-redirect.html
.
直接导航到OIDC授权端点,页面中无需引入Sumsub脚本。适用于CSP禁止第三方脚本、处于纯SSR环境,或你维护了跨多个提供商的通用OIDC客户端的场景。
GET https://id.sumsub.com/api/snsId/oauth/authorize
  ?client_id=<your-client-id>
  &response_type=code
  &redirect_uri=<白名单中的URI>
  &scope=openid+share+name
  &state=<随机CSRF随机数>
  &code_challenge=<base64url(SHA256(code_verifier))>
  &code_challenge_method=S256
  &login_hint=<可选邮箱>
你需要生成
code_verifier
(43–128字符,URL安全),推导
code_challenge = base64url(SHA256(verifier))
,存储verifier(
sessionStorage
),并在回调后将其转发到后端。Sumsub仅接受
code_challenge_method=S256
plain
会被拒绝),且
redirect_uri
必须与白名单字节级匹配
可运行示例:
examples/oidc-redirect.html

Choosing scopes

选择scopes

Default
['openid', 'share', 'name']
. Add
email
,
profile
, or
offline_access
(refresh token) only when downstream logic actually reads them — extra scopes trigger extra checks in the user-facing flow. Full table:
references/scopes-and-claims.md
.
默认值为
['openid', 'share', 'name']
。仅当下游逻辑实际需要时,才添加
email
profile
offline_access
(刷新令牌)——额外的scopes会在用户流程中触发额外的检查。完整列表:
references/scopes-and-claims.md

Stage 2 — Backend: code → access_token

Stage 2 — 后端:code → access_token

Exchange the authorization code on the OIDC token endpoint. The code came from the browser authorize flow, so its counterpart is the standard OIDC token endpoint — authenticated with the OIDC
client_secret
(
client_secret_basic
), not HMAC.
POST https://id.sumsub.com/api/snsId/oauth/token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic base64(<client_id>:<client_secret>)

grant_type=authorization_code
&code=<the code your frontend got>
&redirect_uri=<exact-match URI from Stage 1 — byte-for-byte>
&code_verifier=<PKCE verifier matched to the code_challenge from Stage 1>
Response (24-hour
access_token
):
json
{
  "access_token":  "snd-id-con-a-...",
  "refresh_token": "snd-id-con-r-...",   // only with `offline_access`
  "token_type":    "Bearer",
  "expires_in":    86400,
  "id_token":      "eyJhbGc..."           // only with `openid` — RS256-signed
}
Refresh later via
grant_type: "refresh_token"
; you cannot request more scopes on refresh than were granted at consent time.
The
client_secret
stays server-side — never in the browser bundle. The exchange is
client_secret_basic
/
client_secret_post
; do not HMAC-sign it. (The App-Token + HMAC auth is still used in Stage 3a/4 for the share token and reuse calls.)
在OIDC令牌端点交换授权码。该代码来自浏览器的授权流程,因此对应的是标准OIDC令牌端点——使用OIDC
client_secret
进行认证(
client_secret_basic
),而非HMAC。
POST https://id.sumsub.com/api/snsId/oauth/token
Content-Type: application/x-www-form-urlencoded
Authorization: Basic base64(<client_id>:<client_secret>)

grant_type=authorization_code
&code=<前端获取的code>
&redirect_uri=<与Stage 1完全匹配的URI——字节级匹配>
&code_verifier=<与Stage 1的code_challenge匹配的PKCE verifier>
响应(有效期24小时的
access_token
):
json
{
  "access_token":  "snd-id-con-a-...",
  "refresh_token": "snd-id-con-r-...",   // 仅当使用`offline_access`时返回
  "token_type":    "Bearer",
  "expires_in":    86400,
  "id_token":      "eyJhbGc..."           // 仅当使用`openid`时返回——RS256签名
}
后续可通过
grant_type: "refresh_token"
刷新令牌;刷新时无法请求比授权时更多的scopes。
client_secret
需保留在服务器侧——绝不能出现在浏览器包中。交换使用
client_secret_basic
/
client_secret_post
请勿对其进行HMAC签名。(App-Token + HMAC认证仍用于Stage 3a/4的共享令牌和复用调用。)

Gotchas

常见问题

  • Invalid code
    401
    has three common causes: PKCE mismatch (verifier doesn't hash to the challenge sent at authorize), code already used (single-use; refreshing the callback page double-fires), or code expired (short TTL — exchange immediately).
  • redirect_uri
    must match Stage 1 byte-for-byte
    — trailing slash, casing, port and query all count, or the exchange fails.
  • id_token
    validation
    — use a mature library (
    jose
    ,
    pyjwt
    ,
    jjwt
    ) against the
    jwks_uri
    from
    .well-known/openid-configuration
    . Never trust unverified JWT contents.
  • Invalid code
    401
    有三个常见原因:PKCE不匹配(verifier哈希后与授权时发送的challenge不一致)、代码已被使用(单次有效;刷新回调页面会导致重复触发)、代码过期(TTL较短——需立即交换)。
  • redirect_uri
    必须与Stage 1字节级匹配
    ——末尾斜杠、大小写、端口和查询参数均会影响匹配,否则交换会失败。
  • id_token
    验证
    ——使用成熟的库(
    jose
    pyjwt
    jjwt
    )结合
    .well-known/openid-configuration
    中的
    jwks_uri
    进行验证。切勿信任未验证的JWT内容。

Stage 3 — Backend: access_token → share token + consent

Stage 3 — 后端:access_token → 共享令牌 + 同意流程

Mint a share token scoped to a recipient (
forClientId
). Always implement the consent fallback (Stage 3b)
sharingAllowed: false
is returned whenever the user hasn't yet authorised sharing with this specific
forClientId
. In the common self-recipient case both consents are usually granted in one click on the Stage 1 consent screen, but production code that only handles
sharingAllowed: true
breaks the first time a user re-enters the flow or a recipient configuration differs.
生成针对接收方(
forClientId
)的共享令牌。务必实现同意回退流程(Stage 3b)——当用户尚未授权与该特定
forClientId
共享时,会返回
sharingAllowed: false
。在常见的自接收方场景中,通常会在Stage 1的同意界面中一键授予两个权限,但仅处理
sharingAllowed: true
的生产代码会在用户重新进入流程或接收方配置不同时失效。

Stage 3a — mint share token

Stage 3a — 生成共享令牌

POST https://api.sumsub.com/resources/accessTokens/sumsubIdShareToken
{
  "sumsubIdConnectToken": "<access_token from Stage 2>",
  "forClientId":          "<recipient clientId>",
  "ttlInSecs":            1800
}
forClientId
:
  • Self-recipient (common case): your own workspace
    clientId
    , visible in the Dashboard top-left.
  • Third-party recipient: the partner's
    clientId
    (they must be pre-registered as a partner in the Dashboard).
Response:
json
{
  "token":          "_act-snsId-...",
  "forClientId":    "<echoed>",
  "sharingAllowed": true    // or false → continue to Stage 3b
}
POST https://api.sumsub.com/resources/accessTokens/sumsubIdShareToken
{
  "sumsubIdConnectToken": "<Stage 2获取的access_token>",
  "forClientId":          "<接收方clientId>",
  "ttlInSecs":            1800
}
forClientId
  • 自接收方(常见场景):你自己工作区的
    clientId
    ,可在控制台左上角查看。
  • 第三方接收方:合作伙伴的
    clientId
    (他们必须已在控制台中注册为合作伙伴)。
响应:
json
{
  "token":          "_act-snsId-...",
  "forClientId":    "<回显值>",
  "sharingAllowed": true    // 或false → 进入Stage 3b
}

Stage 3b — partner-consent shareLink

Stage 3b — 合作伙伴同意shareLink

If
sharingAllowed: false
, mint a consent link and redirect the user:
POST https://api.sumsub.com/resources/snsId/oauth/shareLink
Authorization: Bearer <access_token from Stage 2>
X-App-Token + X-App-Access-Ts + X-App-Access-Sig

{
  "redirectUri": "<your shareLink callback URL — must be in the allowlist>",
  "forClientId": "<same recipient clientId>",
  "displayMode": "page",
  "state":       "<csrf-or-correlation-id>"
}
Response:
{ "link": "https://id.sumsub.com/share/<id>" }
— expires in 5 minutes.
Persist the
access_token
server-side keyed by
state
(in-memory Map is OK for demos; use Redis / encrypted cookie / session table for prod). Redirect the user to the
link
. After consent, Sumsub redirects back to
redirectUri
as
?token=<JWT>
— a signed RS256 JWT whose payload carries
state
(alongside
sharingAllowed: true
and
forClientId
).
⚠️
state
is inside the JWT payload, not a top-level
?state=
query param.
Decode the
token
JWT to read
state
back (verify its signature against the
jwks_uri
first), then look up the saved
access_token
by that
state
. Re-call Stage 3a — the share token now returns
sharingAllowed: true
. Decoded payload shape:
{ iss, sub, aud, iat, exp, sharingAllowed, state, forClientId }
.
Full reference implementation:
examples/express-callback.js
(in-memory
pendingShare
Map keyed by
state
,
/share-complete
handler).
sharingAllowed: false
,生成同意链接并重定向用户:
POST https://api.sumsub.com/resources/snsId/oauth/shareLink
Authorization: Bearer <Stage 2获取的access_token>
X-App-Token + X-App-Access-Ts + X-App-Access-Sig

{
  "redirectUri": "<你的shareLink回调URL——必须在白名单中>",
  "forClientId": "<相同的接收方clientId>",
  "displayMode": "page",
  "state":       "<csrf或关联ID>"
}
响应:
{ "link": "https://id.sumsub.com/share/<id>" }
——有效期5分钟。
access_token
在服务器侧以
state
为键存储(演示环境可使用内存Map;生产环境请使用Redis/加密Cookie/会话表)。将用户重定向到该
link
。用户同意后,Sumsub会将用户重定向回
redirectUri
,格式为
?token=<JWT>
——这是一个签名后的RS256 JWT,其payload中包含
state
(同时包含
sharingAllowed: true
forClientId
)。
⚠️
state
在JWT payload中,而非顶级
?state=
查询参数
。解码
token
JWT以读取
state
(先验证其签名与
jwks_uri
匹配),然后通过该
state
查找保存的
access_token
。重新调用Stage 3a——此时共享令牌会返回
sharingAllowed: true
。解码后的payload格式:
{ iss, sub, aud, iat, exp, sharingAllowed, state, forClientId }
完整参考实现:
examples/express-callback.js
(内存
pendingShare
Map以
state
为键,
/share-complete
处理器)。

Stage 4 — Materialise the applicant

Stage 4 — 落地申请人信息

With a usable share token, create the applicant in your workspace. Always preview first.
在获得可用的共享令牌后,在你的工作区中创建申请人信息。务必先预览

Preview (no side effects)

预览(无副作用)

GET https://api.sumsub.com/resources/api/reusableIdentity/reuse/preview
  ?shareToken=<token>
  &levelName=<your-level>
  &userId=<externalUserId>
Returns the same
ApplicantPublicDto
shape that
/reuse
would create — use it to surface compatibility errors before committing.
GET https://api.sumsub.com/resources/api/reusableIdentity/reuse/preview
  ?shareToken=<token>
  &levelName=<你的验证等级>
  &userId=<externalUserId>
返回与
/reuse
创建的
ApplicantPublicDto
相同的格式——可用于在提交前排查兼容性错误。

Commit (creates the applicant)

提交(创建申请人)

POST https://api.sumsub.com/resources/api/reusableIdentity/reuse
  ?shareToken=<token>
  &levelName=<your-level>
  &userId=<externalUserId>
userId
is the
externalUserId
Sumsub stores on the applicant — stable per real user, opaque to the user, tied to your auth system. Same rules as WebSDK.
POST https://api.sumsub.com/resources/api/reusableIdentity/reuse
  ?shareToken=<token>
  &levelName=<你的验证等级>
  &userId=<externalUserId>
userId
是Sumsub存储在申请人信息中的**
externalUserId
**——每个真实用户对应一个稳定值,对用户透明,与你的认证系统绑定。规则与WebSDK相同。

Reuse error codes

复用错误码

All
400
responses include
errorCode
(integer) and
errorName
(kebab-case) suitable for code branching.
401
is generic token validity — no
errorCode
, just
description
.
HTTP / errorCodenameCauseAction
401 / —
Token is invalid
Share token expired (TTL exceeded), already consumed, or malformedRe-mint via Stage 3a — call
/sumsubIdShareToken
again with the still-valid
access_token
400 /
10501
not-in-partners-list
forClientId
is not registered as a sharing partner of your workspace
Add the recipient as a partner in the Dashboard (Sumsub UI only — no public API)
400 /
10502
invalid-partner-id
The partner ID extracted from the share token is invalidRe-check
forClientId
; re-mint share token against a real partner
400 /
10503
invalid-share-token
Share token is malformed or expiredRe-mint via Stage 3a
400 /
10504
non-suitable-share-token
Share token is not suitable for reuse at the given
levelName
(scope mismatch)
Re-mint share token with matching scope, retry against the same level
400 /
10505
reusable-kyc-disabled
Reusable KYC is disabled for this workspaceContact Sumsub support to enable Reusable KYC for your workspace
400 /
10506
reusable-kyc-not-reusable
Generic fallback — donor doesn't meet reuse eligibility and no more specific reason matchedInspect the donor in the Dashboard; pick a different donor or fall back to the WebSDK
400 /
10507
reusable-kyc-not-approved-applicant
Donor applicant is not in an approved state (pending / rejected / on-hold)Wait for donor's KYC to be approved; use a donor with approved status
400 /
10508
reusable-kyc-not-active-applicant
Donor applicant is inactive (deactivated / blocked)Use a different donor — reactivation is Sumsub-side
400 /
10509
reusable-kyc-not-kyc-moderation-type
Donor's source moderation type is not suitable for reuseDonor needs standard KYC moderation; specialised flows aren't reusable
400 /
10510
reusable-kyc-not-selfie-or-identity-doc
Required selfie or identity document is missing on donorPick a level without that doc-set, or have donor re-verify
400 /
10511
reusable-kyc-not-selfie-liveness
Recipient level requires liveness; donor's selfie wasn't captured with livenessUse a level without
videoRequired: passiveLiveness
on SELFIE, or have donor re-verify with liveness
400 /
10512
reusable-kyc-not-req-doc-overlap
Required document types don't overlap between donor and recipient levelsPick a recipient level whose required docs are a subset of donor's
400 /
10513
reusable-kyc-not-compatible-doc-type
Recipient level requires document types the donor doesn't carryUse a less strict level, or fall back to the WebSDK for full capture
400 /
10514
reusable-kyc-not-actual-poi-date
Donor's Proof of Identity is outdated and not valid for reuseDonor must re-submit fresh POI, or relax recipient level's POI freshness window
400 /
10515
reusable-kyc-not-actual-poa-date
Donor's Proof of Address is outdated and not valid for reuseDonor must re-submit fresh POA, or relax recipient level's POA freshness window
400 /
10516
reusable-kyc-not-acceptable-age
Donor doesn't meet the age criteria of the recipient levelNo recovery — different user required
400 /
10517
reusable-kyc-incompatible-capture-settings
Capture settings (file upload vs live camera, etc.) mismatch between donor and recipientAlign recipient level's
captureParams
with what donor used, or have donor re-capture
400 /
10518
reusable-kyc-email-mismatch
Donor's email doesn't match the email already on the recipient applicantResolve duplicate-applicant collision — different
externalUserId
, or align emails
400 /
10519
reusable-kyc-phone-mismatch
Donor's phone doesn't match the phone already on the recipient applicantSame as 10518 — resolve collision
400 /
10521
reusable-kyc-inactive-sumsub-id-account
Donor Sumsub ID account has no stored documents (email-only, KYC never completed)Donor must complete full KYC on
id.sumsub.com
first; for testing, use a Sumsub-provided verified test account
After the applicant is materialised, your
applicantReviewed
webhook is the authoritative signal for verification outcome — wire it up the same way as for the WebSDK. See
sumsub-integrate-websdk
for the webhook material and
sumsub-manage-webhooks
for the sandbox webhook subscription setup.
所有
400
响应均包含
errorCode
(整数)和
errorName
(短横线分隔),适用于代码分支处理。
401
为通用令牌有效性错误——无
errorCode
,仅包含
description
HTTP / errorCode名称原因操作
401 / —
Token is invalid
共享令牌过期(超过TTL)、已被使用或格式错误通过Stage 3a重新生成——使用仍有效的
access_token
再次调用
/sumsubIdShareToken
400 /
10501
not-in-partners-list
forClientId
未注册为你工作区的共享合作伙伴
在控制台中将接收方添加为合作伙伴(仅Sumsub UI操作——无公开API)
400 /
10502
invalid-partner-id
从共享令牌中提取的合作伙伴ID无效重新检查
forClientId
;针对真实合作伙伴重新生成共享令牌
400 /
10503
invalid-share-token
共享令牌格式错误或已过期通过Stage 3a重新生成
400 /
10504
non-suitable-share-token
共享令牌不适用于指定的
levelName
(scope不匹配)
重新生成匹配scope的共享令牌,针对相同等级重试
400 /
10505
reusable-kyc-disabled
工作区已禁用可复用KYC联系Sumsub支持团队为你的工作区启用可复用KYC
400 /
10506
reusable-kyc-not-reusable
通用回退——捐赠方不符合复用条件,且无更具体的原因匹配在控制台中检查捐赠方信息;更换捐赠方或回退到WebSDK
400 /
10507
reusable-kyc-not-approved-applicant
捐赠方申请人未处于已批准状态(待审核/已拒绝/暂停)等待捐赠方KYC审核通过;使用已批准的捐赠方
400 /
10508
reusable-kyc-not-active-applicant
捐赠方申请人已失效(已停用/已封禁)使用其他捐赠方——重新激活需通过Sumsub侧操作
400 /
10509
reusable-kyc-not-kyc-moderation-type
捐赠方的源审核类型不适用于复用捐赠方需要标准KYC审核;特殊流程无法复用
400 /
10510
reusable-kyc-not-selfie-or-identity-doc
捐赠方缺少必需的自拍或身份证件选择无需该文档集的等级,或让捐赠方重新验证
400 /
10511
reusable-kyc-not-selfie-liveness
接收方等级要求活体检测;捐赠方的自拍未进行活体采集使用SELFIE项中无
videoRequired: passiveLiveness
的等级,或让捐赠方重新进行活体验证
400 /
10512
reusable-kyc-not-req-doc-overlap
捐赠方与接收方等级的必需文档类型无重叠选择必需文档为捐赠方子集的接收方等级
400 /
10513
reusable-kyc-not-compatible-doc-type
接收方等级要求的文档类型捐赠方未提供使用要求更低的等级,或回退到WebSDK进行完整采集
400 /
10514
reusable-kyc-not-actual-poi-date
捐赠方的身份证件已过期,无法复用捐赠方必须重新提交有效的身份证件,或放宽接收方等级的身份证件有效期要求
400 /
10515
reusable-kyc-not-actual-poa-date
捐赠方的地址证明已过期,无法复用捐赠方必须重新提交有效的地址证明,或放宽接收方等级的地址证明有效期要求
400 /
10516
reusable-kyc-not-acceptable-age
捐赠方不符合接收方等级的年龄要求无法恢复——需更换用户
400 /
10517
reusable-kyc-incompatible-capture-settings
捐赠方与接收方的采集设置(文件上传vs实时相机等)不匹配对齐接收方等级的
captureParams
与捐赠方使用的设置,或让捐赠方重新采集
400 /
10518
reusable-kyc-email-mismatch
捐赠方的邮箱与接收方申请人已有的邮箱不匹配解决重复申请人冲突——使用不同的
externalUserId
,或对齐邮箱
400 /
10519
reusable-kyc-phone-mismatch
捐赠方的电话与接收方申请人已有的电话不匹配与10518相同——解决冲突
400 /
10521
reusable-kyc-inactive-sumsub-id-account
捐赠方Sumsub ID账户无存储的文档(仅含邮箱,未完成KYC)捐赠方必须先在
id.sumsub.com
完成完整KYC;测试时请使用Sumsub提供的已验证测试账户
申请人信息落地后,
applicantReviewed
webhook是验证结果的权威信号——请按照WebSDK的方式配置。Webhook相关内容请参考
sumsub-integrate-websdk
,沙箱webhook订阅设置请参考
sumsub-manage-webhooks

Going-live checklist

上线清单

When the user says "we're ready for prod":
  • Sumsub support has enabled Sumsub ID Connect for the production
    clientId
    (separate from sandbox).
  • Production
    redirectUri
    s (both authorize and shareLink callbacks) are in the Dashboard allowlist on the prod workspace.
  • Backend token-exchange endpoint is auth-gated (only the authenticated user can exchange a code minted for them).
  • id_token
    signature is verified against the prod JWKS.
  • externalUserId
    is stable per real user (not email, not display name).
  • /reuse/preview
    is called before
    /reuse
    so compatibility errors surface as UX, not 4xx noise.
  • Stage 3b shareLink flow is implemented and tested — don't ship code that only handles
    sharingAllowed: true
    .
  • In-memory state used for the shareLink callback is replaced with a real session store (Redis / encrypted cookie / DB).
  • App Token + secret + OIDC
    client_secret
    are in the prod secret store, not committed and not in browser bundles.
  • Refresh-token storage (if used) is server-side only, encrypted at rest, single-tenant.
当用户表示「我们准备好上线生产环境」时:
  • Sumsub支持团队已为生产环境
    clientId
    启用Sumsub ID Connect(与沙箱环境分离)。
  • 生产环境的
    redirectUri
    (授权和shareLink回调地址)已加入生产工作区的控制台白名单。
  • 后端令牌交换端点已添加认证限制(仅已认证用户可交换为其生成的代码)。
  • id_token
    签名已通过生产环境JWKS验证。
  • externalUserId
    对每个真实用户稳定(非邮箱、非显示名称)。
  • 在调用
    /reuse
    前先调用
    /reuse/preview
    ,以便兼容性错误以用户体验的形式呈现,而非4xx错误。
  • 已实现并测试Stage 3b的shareLink流程——请勿仅处理
    sharingAllowed: true
    的代码上线。
  • shareLink回调使用的内存状态已替换为真实的会话存储(Redis/加密Cookie/数据库)。
  • App Token + 密钥 + OIDC
    client_secret
    已存储在生产环境密钥管理系统中,未提交到代码库且未出现在浏览器包中。
  • 刷新令牌(若使用)仅存储在服务器侧,静态加密,单租户隔离。

See also

另请参阅

  • references/scopes-and-claims.md
    — full scope table, token-endpoint response shape, error codes.
  • scripts/preflight.sh
    — preconditions validator.
  • examples/oidc-button.html
    — Method 1 (
    createButton
    ) — recommended.
  • examples/oidc-modal.html
    — Method 2 (
    openModal
    ).
  • examples/oidc-redirect.html
    — Method 3 (raw OIDC redirect with PKCE).
  • examples/express-callback.js
    — full backend (Stages 2-4 incl. Stage 3b consent flow).
  • sumsub-api-auth
    — auth signing reference.
  • sumsub-create-level
    — when the workspace has no recipient level yet.
  • sumsub-integrate-websdk
    — sibling skill; reuse its webhook material once the applicant is materialised.
  • references/scopes-and-claims.md
    — 完整的scope列表、令牌端点响应格式、错误码。
  • scripts/preflight.sh
    — 前置条件验证脚本。
  • examples/oidc-button.html
    — 方法1(
    createButton
    )——推荐方案。
  • examples/oidc-modal.html
    — 方法2(
    openModal
    )。
  • examples/oidc-redirect.html
    — 方法3(带PKCE的原生OIDC重定向)。
  • examples/express-callback.js
    — 完整后端实现(Stages 2-4,含Stage 3b同意流程)。
  • sumsub-api-auth
    — 认证签名参考。
  • sumsub-create-level
    — 当工作区无接收方等级时使用。
  • sumsub-integrate-websdk
    — 同类方案;申请人信息落地后可复用其webhook内容。