Sumsub Integrate Websdk
End-to-end recipe for adding Sumsub KYC to a website or web app via the Sumsub WebSDK. TRIGGER when the user asks to "integrate / embed / add Sumsub", "show the KYC widget", "add WebSDK", "verify users with Sumsub on the frontend", supplies an existing levelName they want to plug into a page, or asks how to wire up access tokens / lifecycle events / webhooks for Sumsub verification in an arbitrary project. Covers the whole loop — level setup, server-side access-token signing, snsWebSdk init (vanilla canonical, React recipe), client lifecycle events, token refresh, source-of-truth via webhooks + applicant GET, sandbox testing, go-live checklist. SKIP for building the level/questionnaire/POA-preset payload itself (use the sibling skills), or for backend-only API calls with no frontend (use `sumsub-api-generic`).
- Skill ID
- sumsub/agent-skills/sumsub-integrate-websdk
- Publisher
- sumsub
- Repository
- agent-skills
- Installs
- 0
- Files
- 5
- Synced
- Sep 23, 2026
Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.
sumsub/agent-skills/sumsub-integrate-websdkInstalls these files- SKILL.md
- examples/react-component.tsx
- examples/vanilla.html
- examples/webhook-verify.js
- references/lifecycle.md
What this skill tells the agent
Sumsub — WebSDK integration
Embed Sumsub KYC into a web project end-to-end, from level creation to the "applicantReviewed" webhook that gates user access.
⚠️ Sandbox tokens only
Do not accept or use a production App Token / secret during integration work with this skill. The token generates real SDK sessions tied to real applicants. Insist on a sandbox pair from <https://cockpit.sumsub.com/checkus/home?sbx=true> — Connect Sumsub to your AI agent -> Build & configure -> Generate token. Token + secret are revealed once at creation; copy both before closing the dialog. Helper scripts in sibling skills enforce this with an sbx: prefix check; the curl recipes below assume the same.
Deeper auth mechanics: `sumsub-api-auth`.
The lifecycle in one picture
┌─────────────────────────┐
│ 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.
└─────────────────────────┘The split between browser events (UX) and webhooks + server reads (authoritative truth) is the most-missed part of a WebSDK integration. Don't trust onApplicantStatusChanged for entitlement decisions.
Stage 1 — Have a level
Every SDK launch references a levelName that exists in the workspace.
If the user has one (e.g. basic-kyc-level, the Sumsub default), capture it and move to Stage 2.
If the user doesn't yet have a level, brainstorm with them and hand off to `sumsub-create-level`. Don't silently pick defaults — the level encodes who can verify (country / applicant type) and what they must provide (ID, selfie, PoA, questionnaire). A reasonable starter flow when the user is genuinely unsure:
APPLICANT_DATA— name, DOB, country, addresses.IDENTITY—PASSPORT,ID_CARD,DRIVERS(modeany).SELFIE—videoRequired: passiveLiveness.
Add PROOF_OF_RESIDENCE only if regulatory; add QUESTIONNAIRE only if they need structured data (source of funds, occupation). For each addition, ask "what decision does this gate?" before agreeing to include it.
Stage 2 — Server-side access-token endpoint
The SDK needs an access token, generated by your backend with the App Token + secret. The token is short-lived (ttlInSecs, default 1800) and scoped to one (userId, levelName) pair.
Endpoint shape
POST https://api.sumsub.com/resources/accessTokens
?userId=<your-stable-user-id>
&levelName=<level-from-stage-1>
&ttlInSecs=600- Body: empty.
- Auth: App Token + HMAC signature (see `sumsub-api-auth`).
- Response:
{ "token": "_act-sbx-<...>", "userId": "..." }.
userId choice (load-bearing)
This is the `externalUserId` Sumsub stores against the applicant. Make it:
- Stable per real user (don't regenerate on each page load — the SDK looks up returning applicants by this id).
- Opaque to the user (a UUID or DB row id; not their email).
- Tied to your auth system (so a webhook callback can resolve it back to a user record).
Wrong userId choice → duplicate applicants, "stuck in submitted" support tickets, and the inability to resume an interrupted verification.
Curl recipe
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}"URL-encode userId if it might contain /, ?, or &. The signing string must match the URI on the wire exactly — sign the encoded form.
Wiring it into the user's backend
Frame the endpoint as:
- Path: any (e.g.
POST /api/sumsub/access-token). - Inputs: the authenticated user's id, the levelName (often hardcoded per page).
- Auth: user must be logged in to your app — anyone hitting this route can spin up a verification session for that
userId. - Output: forward Sumsub's response body verbatim, or just the
tokenfield. Don't cache it server-side; the browser asks per launch.
Show the snippet for the user's actual stack (Express, FastAPI, Go, etc.) but the contract is the same in all of them: sign, call Sumsub, return token.
Stage 3 — Frontend SDK init
Load the builder
<script src="https://static.sumsub.com/idensic/static/sns-websdk-builder.js"></script>This exposes the global snsWebSdk. For bundler-based projects, an npm package exists but the CDN script is what Sumsub officially documents and what every framework wrapper ends up calling.
Container
<div class="kyc-stage" style="position: relative; min-height: 600px;">
<div id="sumsub-websdk-container"></div>
<!-- Overlay loader covers the empty-iframe window. Hide on idCheck.onReady. -->
<div id="kyc-loader" style="position: absolute; inset: 0; display: grid; place-items: center;">
Loading verification…
</div>
</div>Give the stage a defined min-height (e.g. 600px) so the iframe doesn't collapse before the SDK adapts its height.
Don't skip the overlay loader. Between .launch() returning and the SDK iframe loading content from api.sumsub.com/websdk/websdk.html there is a 1–3s window where the container holds an empty iframe and looks broken — especially inside a modal that the user just opened. Mount a loader that covers the container, then hide it in the idCheck.onReady handler (Stage 4). Treating onReady as informational and leaving the handler empty is the single most common "the widget is blank" report.
Canonical vanilla launch
See `examples/vanilla.html` for a runnable file. Minimal shape:
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.More from sumsub
Use this skill in a project
Open any RiverX project, search the Skills panel for sumsub/agent-skills/sumsub-integrate-websdk, and install it in one click.
