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 ask

By sumsub · 850 installs

npx skills add sumsub/agent-skills --skill sumsub-integrate-websdk

Source repository · Upstream listing

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 ](../sumsub api auth/SKILL.md). The lifecycle in one picture 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 ](../sumsub create level/SKILL.md). 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 Body: empty . Auth: App Token + HMAC signature (see [ sumsub api auth ](../sumsub api auth/SKILL.md)). 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 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 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 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 ](examples/vanilla.html) for a runnable file. Minimal shape: React recipe [ examples/react component.tsx ](examples/react component.tsx) — wraps the same builder in a useEffect with cleanup. Two gotchas it handles: 1. The CDN script must be present before snsWebSdk is read. Either inject it once in the document head, or dynamically load it and await the <script 's load event. 2. On React 18 strict mode in dev, components mount twice — the cleanup function must remove the iframe / clear the container, otherwise you get two stacked widgets. Other frameworks The builder API is framework agnostic. For Vue/Svelte/Angular, mirror the React pattern: lifecycle hook on mount → fetch token → build → launch into a ref'd element; on unmount → empty the container. Stage 4 — Client lifecycle events Wire these handlers on the SDK instance. Treat them as UX signals , not authoritative state. Event When Use it for idCheck.onReady SDK iframe content loaded Hide your own loader. Required — without this the modal looks empty for 1–3s after launch. idCheck.onInitialized First screen rendered Analytics: "user saw KYC step" idCheck.onStepInitiated Doc type screen shown Telemetry per doc type idCheck.onStepCompleted A step finished Progress bar idCheck.onApplicantSubmitted Docs submitted, server is processing Move user to a "waiting" view idCheck.onApplicantStatusChanged Status moved Live progress hint (still not trusted) idCheck.onApplicantResubmitted Re upload after a rejection Re arm waiting view idCheck.onApplicantReviewed (or onApplicantVerificationCompleted in 2.0) Final verdict reached client side Show a preliminary result, then verify server side idCheck.onError SDK error Surface a friendly retry CTA, log code + reason idCheck.onUploadError / onUploadWarning Doc rejected at upload Inline guidance ("blurred photo", etc.) idCheck.onLivenessCompleted (2.0 only) Liveness attempt finished Branch on answer for retry UX idCheck.onResize Frame resized Adjust surrounding layout Full payload fields per event: [ references/lifecycle.md ](references/lifecycle.md). Required handlers for a baseline integration If you wire nothing else, wire these three. Skipping any of them produces a known bad UX: idCheck.onReady → hide the overlay loader from Stage 3. Without this the modal looks blank for 1–3s after .launch() . idCheck.onApplicantSubmitted → move the user to a "we're reviewing" state. Without this the user re uploads or contacts support. idCheck.onError → render a retryable error to the UI. Without this failures only land in console.error and the user sees a stuck loader. Why you can't trust onApplicantReviewed alone The browser event fires from inside the iframe. A bad actor can spoof it trivially. The only authoritative signal is server side : either a webhook delivery (Stage 5) or an authenticated GET against /resources/applicants/{userId}/one . Stage 5 — Server side source of truth Webhook receiver Sumsub POSTs JSON to your URL on every event. Two paths for registering it: Sandbox (while building this integration): use the [ sumsub manage webhooks ](../sumsub manage webhooks/SKILL.md) skill. It builds the clientWebhooks payload from a compact spec, POSTs to /resources/api/agent/clientWebhooks with App Token auth, refuses non sbx: tokens, rejects localhost / 127.0.0.1 targets up front, and walks the user through exposing their local receiver via ngrok http <port so Sumsub can actually reach it. Hand off the target , types[] , and signatureAlgorithm the user wants and let that skill do the POST. Production: do not create the prod webhook from any skill, including this one. Production webhook setup must be done by a human directly in the Sumsub dashboard (Integrations → Webhooks, workspace toggle on Production ). The signing secret authenticates real PII deliveries; the audit trail should attribute setup to a person. Prototype the spec against sandbox here, then hand the final settings (target, event list, signature algorithm, custom headers) to whoever has prod access to recreate manually. Headers you care about: x payload digest — the signature, hex encoded. x payload digest alg — HMAC SHA256 HEX (default), HMAC SHA512 HEX , or the legacy HMAC SHA1 HEX . The signing secret is not your App Token secret . It's a separate webhook secret generated (or supplied) at webhook creation time in the dashboard. Store it in env ( SUMSUB WEBHOOK SECRET ) alongside the App Token pair. Verification recipe — note the raw bytes requirement; do NOT JSON parse before computing the digest, because re serialising changes whitespace and key order: See [ examples/webhook verify.js ](examples/webhook verify.js) for a complete handler. Local testing with ngrok Sumsub needs a publicly reachable URL — your laptop's localhost won't do. The fastest end to end loop for local dev: webhook.site works too if you just want to see the raw payload without running a local receiver — but it can't echo back a 200 to verify the delivery flow. Webhook events that matter for a KYC flow type What it means Action applicantCreated First time you minted a token for this externalUserId Log; nothing required applicantPending User finished uploading; Sumsub is checking Show "in review" applicantPrechecked Primary data processing done, queued for human/AML Still "in review" applicantOnHold Paused (often AML hit needing analyst) Surface to ops; tell user "extra checks" applicantReviewed Final verdict — reviewResult.reviewAnswer is GREEN or RED Gate access here. Mark user verified or rejected. applicantPersonalInfoChanged User edited info after submission Re check before granting access applicantWorkflowCompleted Whole workflow (multi level) done Same as applicantReviewed for single level flows applicantActionPending / Reviewed One off action (separate from the level flow) Per action handling reviewResult.reviewAnswer : GREEN — approved. RED — rejected. rejectLabels says why; reviewRejectType is FINAL (can't retry) or RETRY (user may resubmit). Full list: [Sumsub webhook docs](https://docs.sumsub.com/docs/user verification webhooks). Server side status check (fallback / on demand) For pages that need to check status synchronously (e.g. user logs back in between webhook arriving and your DB updating): Reads reviewStatus and reviewResult.reviewAnswer . Cheap; use as a fallback, not as a polling loop — webhooks are the primary signal. Resumption / returning users The SDK looks up applicants by externalUserId . If a user starts verification, abandons, and returns 3 days later: 1. Your endpoint mints a new access token for the same userId + same levelName . 2. The SDK opens to wherever the user left off (re uploads only the missing steps). 3. No duplicate applicant is created. Don't generate a new userId for returning users — that's the 1 cause of "the user is stuck and customer support sees two applicants". Token refresh The first argume