sumsub/agent-skills5 files

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`).

Specification
Skill ID
sumsub/agent-skills/sumsub-integrate-websdk
Publisher
sumsub
Repository
agent-skills
Installs
0
Files
5
Synced
Sep 23, 2026
How to use it

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 (mode any).
  • 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 token field. 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.

Use this skill