sumsub-integrate-websdk について
Sumsub WebSDK をウェブアプリに組み込むためのスキルで、トークン発行、ブラウザー側の設定、検証コールバックを扱います。サンドボックス認証情報の使用と、KYC 判定におけるサーバー側検証を重視します。
主な機能
- 01サーバー側アクセストークンエンドポイントのガイド
- 02Vanilla JavaScript と React の起動例
- 03クライアントのライフサイクルイベント処理
- 04Webhook 署名の検証
- 05サンドボックス優先の統合ガイド
活用シーン
- 01ウェブ登録フローへの Sumsub KYC の追加
- 02検証状況やエラーの表示
- 03Webhook による KYC 結果の確認
- 04ローカル開発時の Webhook 配信テスト
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ sumsub/ agent-skills --skill sumsub-integrate-websdkインストール後、Claude Code で/sumsub-integrate-websdkを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
examples/react-component.tsxvanilla.htmlwebhook-verify.jsreferences/lifecycle.md詳細
Sumsub — WebSDK の統合
レベルの作成から、ユーザーアクセスの許可を制御する "applicantReviewed" webhook まで、Sumsub KYC をウェブプロジェクトに エンドツーエンドで組み込みます。
⚠️ サンドボックストークンのみ
このスキルを使った統合作業では、本番環境の App Token / secret を
受け入れたり使用したりしないでください。このトークンは、実在の申請者に紐付いた
実際の SDK セッションを生成します。
https://cockpit.sumsub.com/checkus/home?sbx=true から サンドボックスのペアを
必ず入手してください — Connect Sumsub to
your AI agent -> Build & configure -> Generate token。トークンと secret は
作成時に一度だけ表示されるため、ダイアログを閉じる前に両方をコピーしてください。同じディレクトリにある
スキルのヘルパースクリプトは sbx: プレフィックスのチェックでこれを強制します。以下の curl の例も
同じ前提です。
認証の詳細については、sumsub-api-auth を参照してください。
ライフサイクルの全体像
┌─────────────────────────┐
│ 1. Level exists in the │ ← one-time, done in dashboard or
│ workspace │ via sumsub-create-level
└─────────────┬───────────┘
│ levelName
┌─────────────▼───────────┐ ┌──────────────────────────────────┐
│ 2. Server-side token │◀─┤ Browser calls /api/sumsub/token │
│ endpoint (HMAC-signed │ └──────────────────────────────────┘
│ POST /resources/ │
│ accessTokens) │
└─────────────┬───────────┘
│ {token, userId}
┌─────────────▼───────────┐
│ 3. Browser: snsWebSdk │ ← user fills doc capture / selfie / form
│ init → build → launch │ events fire: onApplicantSubmitted, etc.
└─────────────┬───────────┘
│ documents submitted
┌─────────────▼───────────┐ ┌──────────────────────────────────┐
│ 4. Sumsub runs checks │─▶│ Webhook POST → your server │
│ (async, ~seconds–min) │ │ (applicantReviewed = the truth) │
└─────────────┬───────────┘ └──────────────────────────────────┘
│ verdict
┌─────────────▼───────────┐
│ 5. Your app gates access │ ← server checks reviewAnswer, not
│ by reading applicant │ the browser. Browser events are
│ via GET /applicants… │ UX only.
└─────────────────────────┘
ブラウザーイベント(UX)と webhook + サーバー読み取り
(信頼できる情報)を分けることが、WebSDK 統合で最も見落とされやすいポイントです。アクセス権の判断に
onApplicantStatusChanged を信頼しないでください。
ステージ 1 — レベルを用意する
すべての SDK 起動では、ワークスペースに存在する levelName を参照します。
ユーザーがレベルを持っている場合(例:Sumsub のデフォルトである basic-kyc-level)は、それを確認して
ステージ 2 に進みます。
ユーザーがまだレベルを持っていない場合は、ユーザーと相談し、
sumsub-create-level に引き継いでください。デフォルトを勝手に選ばないでください。
レベルには、誰が本人確認できるか(国 / 申請者の種類)と、何を提出する必要があるか(ID、セルフィー、住所証明、アンケート)が設定されています。
ユーザーが本当に判断に迷っている場合は、次のような基本フローが妥当です。
APPLICANT_DATA— 氏名、生年月日、国、住所。IDENTITY—PASSPORT、ID_CARD、DRIVERS(モードany)。SELFIE—videoRequired: passiveLiveness。
規制上必要な場合に限り PROOF_OF_RESIDENCE を追加してください。体系化されたデータ(資金源、職業)が必要な場合に限り
QUESTIONNAIRE を追加してください。項目を追加するたびに、含めることに同意する前に
「この条件で何を判断しますか?」と尋ねてください。
ステージ 2 — サーバー側のアクセストークンエンドポイント
SDK には、App Token + secret を使ってバックエンドが生成するアクセストークンが必要です。
トークンの有効期間は短く(ttlInSecs、デフォルトは 1800)、1 つの (userId, levelName) の組み合わせにスコープが限定されます。
エンドポイントの形式
POST https://api.sumsub.com/resources/accessTokens
?userId=<your-stable-user-id>
&levelName=<level-from-stage-1>
&ttlInSecs=600
- Body:空。
- Auth:App Token + HMAC 署名(
sumsub-api-authを参照)。 - Response:
{ "token": "_act-sbx-<...>", "userId": "..." }。
userId の選択(重要)
これは、Sumsub が申請者に紐付けて保存する externalUserId です。次のようにしてください。
- 実ユーザーごとに安定させる(ページを読み込むたびに再生成しないでください。SDK はこの ID で再訪した申請者を検索します)。
- ユーザーにとって推測しにくい値にする(UUID または DB の行 ID を使い、メールアドレスは使わないでください)。
- 認証システムに紐付ける(webhook のコールバックからユーザーレコードを特定できるようにします)。
userId の選択を誤ると、申請者が重複し、「submitted のまま進まない」というサポートチケットが発生し、
中断された本人確認を再開できなくなります。
Curl の例
SUMSUB_APP_TOKEN='sbx:...'
SUMSUB_SECRET_KEY='...'
USER_ID='u-12345'
LEVEL='basic-kyc-level'
PATH_Q="/resources/accessTokens?userId=${USER_ID}&levelName=${LEVEL}&ttlInSecs=600"
TS=$(date -u +%s)
SIG=$(printf '%s%s%s' "$TS" "POST" "$PATH_Q" \
| openssl dgst -sha256 -hmac "$SUMSUB_SECRET_KEY" -hex \
| awk '{print $NF}')
curl -sS -X POST \
-H "X-App-Token: $SUMSUB_APP_TOKEN" \
-H "X-App-Access-Ts: $TS" \
-H "X-App-Access-Sig: $SIG" \
"https://api.sumsub.com${PATH_Q}"
userId に /、?、& が含まれる可能性がある場合は URL エンコードしてください。署名文字列は、
実際に送信する URI と完全に一致している必要があります — エンコードした形式に署名してください。
ユーザーのバックエンドへの組み込み
エンドポイントは次のように説明してください。
- Path:任意(例:
POST /api/sumsub/access-token)。 - Inputs:認証済みユーザーの ID、levelName(ページごとにハードコードすることがよくあります)。
- Auth:ユーザーはアプリにログインしている必要があります — このルートにアクセスできる人なら誰でも、その
userIdの本人確認セッションを開始できます。 - Output:Sumsub のレスポンスボディをそのまま返すか、
tokenフィールドだけを返します。サーバー側ではキャッシュしないでください。ブラウザーは起動のたびにリクエストします。
ユーザーが実際に使っているスタック(Express、FastAPI、Go など)に合わせたコード例を示してください。 ただし、どのスタックでも契約は同じです。署名し、Sumsub を呼び出し、トークンを返します。
ステージ 3 — フロントエンド SDK の初期化
ビルダーの読み込み
<script src="https://static.sumsub.com/idensic/static/sns-websdk-builder.js"></script>
これにより、グローバルの snsWebSdk が公開されます。バンドラーを使うプロジェクトには npm
パッケージもありますが、Sumsub が公式にドキュメント化しており、あらゆるフレームワークラッパーが最終的に呼び出すのは CDN スクリプトです。
コンテナ
<div class="kyc-stage" style="position: relative; min-height: 600px;">
<div id="sumsub-websdk-container"></div>
<div id="kyc-loader" style="position: absolute; inset: 0; display: grid; place-items: center;">
Loading verification…
</div>
</div>
SDK が高さを調整するまで iframe が折りたたまれないよう、ステージに明示的な min-height(例:600px)を設定してください。
オーバーレイローダーを省略しないでください。 .launch() が戻ってから、SDK の iframe が api.sumsub.com/websdk/websdk.html からコンテンツを読み込むまでの間に、1~3 秒の空白があります。その間、コンテナには空の iframe が表示され、壊れているように見えます。ユーザーが開いたばかりのモーダルでは、特に目立ちます。コンテナを覆うローダーをマウントし、idCheck.onReady ハンドラー(ステージ 4)で非表示にしてください。onReady を情報通知として扱ってハンドラーを空のままにすることが、「ウィジェットが真っ白」という報告の最も一般的な原因です。
標準的な Vanilla 起動方法
実行可能なファイルについては、examples/vanilla.html を参照してください。
最小構成:
async function getAccessToken() {
const r = await fetch('/api/sumsub/access-token', { method: 'POST' });
if (!r.ok) throw new Error('failed to mint access token');
return (await r.json()).token;
}
const initialToken = await getAccessToken();
const sdk = snsWebSdk
.init(initialToken, () => getAccessToken()) // refresh callback, returns Promise<string>
.withConf({
lang: 'en',
email: currentUser.email, // optional, prefills
phone: currentUser.phone, // optional, prefills
theme: 'light', // 'light' | 'dark'
})
.withOptions({
addViewportTag: false, // host page already sets it
adaptIframeHeight: true,
})
.on('idCheck.onReady', () => {
// SDK iframe content loaded — hide the overlay loader from the container snippet.
document.getElementById('kyc-loader')?.style.setProperty('display', 'none');
})
.on('idCheck.onApplicantSubmitted', () => {
// user just finished uploading; show "we're reviewing"
})
.on('idCheck.onApplicantStatusChanged', (payload) => {
// status moved; payload.reviewStatus = 'pending' | 'queued' | 'completed' | ...
})
.on('idCheck.onError', (err) => {
console.error('sumsub error', err);
})
.onMessage((type, payload) => {
// catch-all firehose — useful for analytics or debugging
})
.build();
sdk.launch('#sumsub-websdk-container');
React のレシピ
examples/react-component.tsx — 同じビルダーをクリーンアップ処理付きの useEffect でラップしています。対処している注意点は 2 つあります。
snsWebSdkを読み込む前に、CDN スクリプトが存在している必要があります。ドキュメントの head に一度だけ挿入するか、動的に読み込んで<script>のloadイベントをawaitしてください。- 開発時に React 18 の Strict Mode を使うと、コンポーネントは 2 回マウントされます。クリーンアップ関数で iframe を削除するか、コンテナを空にしてください。そうしないと、ウィジェットが 2 つ重なって表示されます。
その他のフレームワーク
ビルダー API はフレームワークに依存しません。Vue/Svelte/Angular では、React のパターンにならってください。マウント時のライフサイクルフック → トークンを取得 → ビルド → ref で参照する要素に起動し、アンマウント時にコンテナを空にします。
ステージ 4 — クライアントのライフサイクルイベント
SDK インスタンスに以下のハンドラーを設定してください。これらはUX シグナルとして扱い、確定的な状態として扱わないでください。
| イベント | 発生タイミング | 用途 |
|---|---|---|
idCheck.onReady | SDK の iframe コンテンツが読み込まれたとき | 独自のローダーを非表示にします。 必須です。設定しないと、起動後 1~3 秒間、モーダルが空に見えます。 |
idCheck.onInitialized | 最初の画面が描画されたとき | 分析:「ユーザーに KYC のステップが表示された」 |
idCheck.onStepInitiated | 書類の種類を選ぶ画面が表示されたとき | 書類の種類ごとのテレメトリー |
idCheck.onStepCompleted | ステップが完了したとき | 進行状況バー |
idCheck.onApplicantSubmitted | 書類が送信され、サーバーが処理中のとき | ユーザーを「確認待ち」画面に移動 |
idCheck.onApplicantStatusChanged | ステータスが変わったとき | 進行状況のライブヒント(ただし、依然として信頼できる情報ではありません) |
idCheck.onApplicantResubmitted | 却下後に再アップロードされたとき | 確認待ち画面を再度表示 |
idCheck.onApplicantReviewed(2.0 では onApplicantVerificationCompleted) | クライアント側で最終判定に達したとき | 暫定的な結果を表示してから、サーバー側で検証 |
idCheck.onError | SDK エラーが発生したとき | わかりやすい再試行 CTA を表示し、code と reason を記録 |
idCheck.onUploadError/onUploadWarning | アップロード時に書類が却下されたとき | インラインで案内(「写真がぼやけています」など) |
idCheck.onLivenessCompleted(2.0 のみ) | ライブネス確認が完了したとき | UX の再試行方法を answer に応じて分岐 |
idCheck.onResize | フレームのサイズが変わったとき | 周囲のレイアウトを調整 |
イベントごとの全ペイロードフィールドについては、references/lifecycle.md を参照してください。
基本的な統合に必須のハンドラー
ほかに何も設定しない場合でも、この 3 つは設定してください。どれか 1 つでも省略すると、既知の問題がある UX になります。
idCheck.onReady→ ステージ 3 のオーバーレイローダーを非表示にします。これを設定しないと、.launch()後 1~3 秒間、モーダルが空に見えます。idCheck.onApplicantSubmitted→ ユーザーを「確認中」の状態に移動します。これを設定しないと、ユーザーは再アップロードしたり、サポートに問い合わせたりします。idCheck.onError→ 再試行可能なエラーを UI に表示します。これを設定しないと、エラーはconsole.errorに出力されるだけで、ユーザーにはローダーが止まったままに見えます。
onApplicantReviewed だけでは信頼できない理由
ブラウザーのイベントは iframe 内から発火します。悪意のあるユーザーは簡単に偽装できます。信頼できる唯一のシグナルはサーバー側のものです。ステージ 5 の Webhook 配信か、認証済みの GET による /resources/applicants/{userId}/one へのリクエストです。
ステージ 5 — サーバー側を信頼できる情報源にする
Webhook 受信側
Sumsub はイベントごとに、指定した URL に JSON を POST します。登録方法は 2 通りあります。
- Sandbox(この連携の構築中):
sumsub-manage-webhooksスキルを使用します。簡潔な仕様からclientWebhooksペイロードを作成し、App Token 認証で/resources/api/agent/clientWebhooksに POST します。また、sbx:以外のトークンを拒否し、localhost/127.0.0.1を指定したターゲットを事前に拒否したうえで、Sumsub から実際にアクセスできるよう、ngrok http <port>を使ってローカルの受信側を公開する手順をユーザーに案内します。ユーザーが希望するtarget、types[]、signatureAlgorithmを渡し、POST はそのスキルに任せてください。 - Production: このスキルを含め、どのスキルからも本番用 webhook を作成しないでください。本番用 webhook の設定は、人が Sumsub ダッシュボードで直接行う必要があります(Integrations → Webhooks、ワークスペースの切り替えで Production を選択)。署名シークレットは実際の PII 配信を認証するため、監査証跡には設定を行った人物が記録されるべきです。ここでは sandbox で仕様を試作し、最終設定(ターゲット、イベント一覧、署名アルゴリズム、カスタムヘッダー)を、本番環境へのアクセス権を持つ担当者に渡して手動で再作成してもらってください。
確認するヘッダーは次のとおりです。
x-payload-digest— 16 進数でエンコードされた署名です。x-payload-digest-alg—HMAC_SHA256_HEX(デフォルト)、HMAC_SHA512_HEX、または従来のHMAC_SHA1_HEXです。
署名シークレットは App Token シークレットではありません。 ダッシュボードで webhook の作成時に生成(または指定)する、別のシークレットです。App Token のペアとあわせて、環境変数(SUMSUB_WEBHOOK_SECRET)に保存してください。
検証手順 — 生のバイト列が必要です。ダイジェストを計算する前に JSON をパースしないでください。再シリアライズすると空白やキーの順序が変わるためです。
// Node/Express — bodyParser.raw() so req.body is a Buffer
import crypto from 'node:crypto';
const ALG = { HMAC_SHA1_HEX: 'sha1', HMAC_SHA256_HEX: 'sha256', HMAC_SHA512_HEX: 'sha512' };
function verifySumsubWebhook(req, secret) {
const alg = ALG[req.header('x-payload-digest-alg') || 'HMAC_SHA256_HEX'];
const expected = req.header('x-payload-digest');
const actual = crypto.createHmac(alg, secret).update(req.body).digest('hex');
return Buffer.from(actual, 'hex').length === Buffer.from(expected, 'hex').length
&& crypto.timingSafeEqual(Buffer.from(actual, 'hex'), Buffer.from(expected, 'hex'));
}
完全なハンドラーについては、examples/webhook-verify.js を参照してください。
ngrok を使ったローカルテスト
Sumsub には外部からアクセス可能な URL が必要です。ノート PC の localhost では使えません。ローカル開発で最も手早くエンドツーエンドのテストを行う手順は次のとおりです。
# 1. In one shell, start your local receiver (Node, Python, whatever).
node server.js # listens on http://localhost:3000
# 2. In another shell, tunnel that port.
ngrok http 3000 # prints https://<random>.ngrok-free.app -> http://localhost:3000
# 3. Register the webhook against the ngrok URL.
# Either via the dashboard (Integrations → Webhooks) OR via
# sumsub-manage-webhooks `create` with target = the ngrok https URL.
# 4. Trigger an event by running a sandbox WebSDK verification end-to-end.
# Watch the request arrive in your local server logs.
# 5. When you're happy, PATCH the webhook to point at your real server
# hostname (sumsub-manage-webhooks `update` command).
ローカルの受信側を起動せずに生のペイロードを確認したいだけであれば、webhook.site も使えます。ただし、配信フローを検証するための 200 を返すことはできません。
KYC フローで重要な Webhook イベント
type | 意味 | 対応 |
|---|---|---|
applicantCreated | この externalUserId のトークンを初めて発行した | ログに記録。対応は不要です |
applicantPending | ユーザーがアップロードを完了し、Sumsub が確認中です | 「審査中」と表示します |
applicantPrechecked | 主要なデータ処理が完了し、人による確認/AML 審査の待ち行列に入りました | 引き続き「審査中」とします |
applicantOnHold | 一時停止中(多くの場合、アナリストの対応が必要な AML の該当)です | 運用チームに通知し、ユーザーには「追加確認」と伝えます |
applicantReviewed | 最終判定です。reviewResult.reviewAnswer は GREEN または RED です | ここでアクセスを制御します。 ユーザーを認証済みまたは却下として記録します |
applicantPersonalInfoChanged | ユーザーが提出後に情報を編集しました | アクセスを許可する前に再確認します |
applicantWorkflowCompleted | ワークフロー全体(複数レベル)が完了しました | 単一レベルのフローでは applicantReviewed と同じ扱いです |
applicantActionPending / Reviewed | 単発のアクションです(レベルフローとは別です) | アクションごとに処理します |
reviewResult.reviewAnswer:
GREEN— 承認済みです。RED— 却下されました。rejectLabelsに理由が記載されます。reviewRejectTypeはFINAL(再試行不可)またはRETRY(ユーザーは再提出可能)です。
全リスト:Sumsub webhook のドキュメント。
サーバー側のステータス確認(フォールバック/オンデマンド)
Webhook の到着後、データベースが更新されるまでの間にユーザーが再ログインした場合など、ステータスを同期的に確認する必要があるページでは、次のようにします。
PATH_Q="/resources/applicants/${USER_ID}/one"
TS=$(date -u +%s)
SIG=$(printf '%s%s%s' "$TS" "GET" "$PATH_Q" \
| openssl dgst -sha256 -hmac "$SUMSUB_SECRET_KEY" -hex \
| awk '{print $NF}')
curl -sS \
-H "X-App-Token: $SUMSUB_APP_TOKEN" \
-H "X-App-Access-Ts: $TS" \
-H "X-App-Access-Sig: $SIG" \
"https://api.sumsub.com${PATH_Q}"
reviewStatus と reviewResult.reviewAnswer を読み取ります。負荷は軽いため、ポーリングループではなくフォールバックとして使用してください。主な通知手段は Webhook です。
再開/再訪ユーザー
SDK は externalUserId を使って申請者を検索します。ユーザーが認証を開始して途中でやめ、3 日後に戻ってきた場合:
- エンドポイントで、同じ
userIdと同じlevelNameに対する新しいアクセストークンを発行します。 - SDK はユーザーが前回中断したところから開きます(不足しているステップだけを再アップロードします)。
- 申請者の重複は作成されません。
再訪ユーザーに新しい userId を生成しないでください。「ユーザーが先に進めず、カスタマーサポートには申請者が 2 人表示される」最大の原因です。
トークンの更新
.init(token, refreshCallback) の最初の引数は、セッション途中での有効期限切れに対応します。
- トークンの有効期限が近づくと、SDK は
refreshCallbackを呼び出します。 Promise<string>を返し、新しいトークンに解決する必要があります(エンドポイントを再度呼び出します)。- 古いトークンや誤った
userIdのトークンを返すと、SDK は停止します。
ほとんどのフローでは、TTL が 600 秒あれば十分です。事前に取得してキャッシュせず、必要に応じて発行してください。
サンドボックスでのテスト
サンドボックスモードでは:
- Sumsub のドキュメントの「Test documents」ページにあるテスト書類を使うと、実際の PII をアップロードせずに
GREENとREDの結果を発生させられます。 - AML ヒットはシミュレートされます。
Greenacreなどの名前は通過し、Aikmanなどは事前登録された陽性一致として扱われます。 - Webhook の配信は通常と同じように機能します。ローカルの受信先を使う場合は、
ngrok http <port>(または Cloudflare Tunnel / Tailscale Funnel)で公開し、sumsub-manage-webhooksスキルを使って公開 URL を登録してください。このスキルには手順の詳細が記載されており、localhostのみを指定したターゲットは Sumsub に到達する前に拒否されます。実際の受信先を使わずにペイロードの形式を確認する場合は、webhook.siteを使用できます。 - サンドボックスではセルフィーの生体認証は実行されず、どの顔でも使用できます。
サンドボックストークン(sbx:)はサンドボックスワークスペースに対してのみ機能します。本番トークン(prd:)は本番環境に対してのみ機能します。相互にフォールスルーすることはありません。
本番環境への移行チェックリスト
ユーザーが「本番環境に切り替える準備ができました」と言ったとき:
- Webhook の受信先で、未加工のバイト列に対する署名を検証する(実際の配信でステージ 5 のテストを再実行します)。
-
externalUserIdにはメールアドレスや表示名ではなく、安定したユーザー ID を指定する。 - 認証状態の信頼できる情報源はサーバーにする。ブラウザーイベントは完全に無視してもかまいません。
- トークンエンドポイントを認証で保護する(ログイン済みユーザーが自分自身のためにのみトークンを発行できるようにする)。
-
applicantReviewedによって、冪等性を確保したうえで、データベース内のユーザー向け状態を変更する(同じイベントが 2 回届く場合があります)。 - ユーザーごとの再試行を処理する:
reviewResult.reviewRejectType === 'RETRY'⇒ ユーザーが SDK を再起動できるようにする。'FINAL'⇒ ブロックする。 - 本番用 App Token、シークレット、個別の Webhook シークレットをすべて本番用シークレットストアに保存する。サンドボックスの値は開発環境にのみ残します。
関連項目
references/lifecycle.md— ペイロードフィールドを含むイベントカタログの全体、および Webhook イベントのリファレンス。examples/vanilla.html— 実行可能な単一ファイルのインテグレーション。examples/react-component.tsx— React のフックとクリーンアップのパターン。examples/webhook-verify.js— リクエストボディを未加工のまま扱う署名検証。sumsub-api-auth— 他のすべての Sumsub スキルと共通の認証署名リファレンス。sumsub-create-level— ステージ 1 の引き継ぎについて。sumsub-manage-webhooks— ステージ 5 の引き継ぎについて:ローカルホストの拒否と ngrok の手順を組み込んだ公開 API を使って、サンドボックス Webhook を作成/一覧表示/更新/無効化します。本番 Webhook の設定はダッシュボードでのみ行え、人間が実施する必要があります。- Sumsub docs index — このスキルの記載と異なる点があれば、こちらを正式な情報源としてください。