Sumsub — Device Intelligence standalone (no WebSDK)
Run
Device Intelligence (DI) on your own pages that have no Sumsub
verification widget — logins, signups, password resets, 2FA, checkouts. You
embed the
Fisherman JS module (npm
) directly, capture
the device on a user action, and confirm the action to Sumsub so it can screen
for device fraud (bots, emulators, VPN/proxy, reused devices, multi-accounting).
This is Sumsub's
ongoing-monitoring / pre-KYC path. If your page already
shows the Sumsub WebSDK, stop — DI is built into it, use
sumsub-integrate-dint-websdk
instead.
⚠️ Sandbox tokens only
Do
not accept or use a production App Token / secret during integration
work. 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 curl recipes here assume an
token; the preflight enforces it with an
prefix check. Deeper auth
mechanics:
.
Before you start — fit DI into the existing app
DI should look like it was always part of their codebase — their framework, their
components, their styling, their conventions. Don't scaffold over someone's app
or introduce a new design system. Read enough of the repo to match it, then ask only
what you can't infer:
- Which action to screen? The high-value submit to hook DI onto — login,
signup, withdrawal, checkout, 2FA, password reset. (Show the ones you found in
their routes/components as suggestions.)
- Where's the backend? The server file/route that can mint the behavior token
and sign the Stage-3 confirm — HMAC must stay server-side. If they're
frontend-only (SPA, no backend), flag that they need a server endpoint and offer
to add a minimal one in their stack.
- Existing Sumsub footprint? Do they already call Sumsub / have App Token +
secret wired, an convention, a webhook receiver? Reuse it; don't
duplicate.
- How are secrets / env handled? Match their existing pattern (, secrets
manager, config service) for / — sandbox
values only during integration.
- TypeScript or JS? Match the repo; don't add TS to a JS project or vice-versa.
Map the chosen action to its Stage-3 confirm event:
| Action | Stage-3 confirm event |
|---|
| Login / signup / password reset / 2FA | platform event |
| Money movement (transfer, withdrawal, deposit, payment) | platform event + financial txn |
| Pre-KYC signup where you also create the applicant | pre-KYC create-applicant |
| Guest / high-value checkout | financial txn |
Start with
Stage 0 (preflight) to confirm the workspace has the DI entitlement
before writing any code. Then implement Stages 1–4 against
their files: a token
route in their backend style,
on their existing submit
handler (fail-open), the server-side confirm with the event mapped from the chosen
action. Touch the minimum needed; follow surrounding code style.
The loop in one picture
┌─────────────────────────────┐ ┌──────────────────────────────────────┐
│ 1. Browser hits your page │─▶│ Your backend: POST .../accessTokens/ │
│ (login / signup form) │ │ behavior {sessionId} → {token} │
└─────────────┬───────────────┘ └──────────────────────────────────────┘
│ behavior access token
┌─────────────▼───────────────┐
│ 2. Browser: @sumsub/fisherman│ ← init(token) → fingerprint() on submit
│ captures the device │ returns { visitorId }
└─────────────┬───────────────┘
│ user submits (login succeeds / account created)
┌─────────────▼───────────────┐ ┌──────────────────────────────────────┐
│ 3. Your backend confirms the │─▶│ Sumsub runs DI analysis on the event │
│ event w/ captured device │ │ (platform event / txn / applicant) │
└─────────────┬───────────────┘ └──────────────────────────────────────┘
│ verdict + device signals
┌─────────────▼───────────────┐ ← Dashboard → Transactions (User
│ 4. You read results / act │ platform event); or webhook
└──────────────────────────────┘
The headline: capturing the device is inert until you confirm the event
server-side (Stage 3). Capturing alone produces no verdict — the confirm call
is what makes Sumsub run the analysis.
Vocabulary (get these right — they're load-bearing)
| Term | What it is | Who sets it |
|---|
| The correlation key. Ties every fingerprint + event in one DI session together, and is the session the behavior token is minted with — so the Stage-3 confirm correlates the captured device to the event server-side, with no device fields in the body. | Your backend — generate one per logical session, opaque & unique. |
| Optional alternative linking id. Sumsub is migrating device linking from to ; on the default flow it is not required. If you use it: pass it to , mint the token with it, and send it at applicant.device.deviceBindingId
in the confirm body. | Your frontend — a fresh per action. |
| Optional — links the fingerprint to an entity (your user id / applicant id). | Frontend (optional). |
| The device identifier returns; surfaces in results. On the standalone confirm flow you do not forward it — the token's session ties the capture to the event. | Returned by Fisherman. |
Stage 0 — Preflight
machine-validates the workspace
before you write any frontend code:
bash
SUMSUB_APP_TOKEN=sbx:... SUMSUB_SECRET_KEY=... \
bash scripts/preflight.sh
| Check | What it proves |
|---|
| HMAC signing is accepted and Device Intelligence is enabled for this workspace — mints a real short-TTL behavior token (discarded, expires on its own; nothing else is mutated) |
| Verification levels exist. WARN-only — just the pre-KYC confirm path (create-applicant) needs one; platform events work without it |
Exit code:
on all-PASS / PASS+WARN,
on any FAIL. A
on
usually means the
DI entitlement is off for the
workspace — it's not self-serve; ask your CSM or
to
enable Device Intelligence, mentioning your
(Dashboard top-left)
and environment (sandbox/production). Resolve all FAILs before Stage 1.
Stage 1 — Backend: mint a behavior access token
Separate endpoint from the WebSDK token — note the
suffix and the
JSON body:
POST https://api.sumsub.com/resources/accessTokens/behavior
Content-Type: application/json
{ "sessionId": "<your-session-id>", "ttlInSecs": 1800 }
- Auth: App Token + HMAC. The body is part of the signature — sign
ts + "POST" + path + body
(the empty-body shortcut from the WebSDK skill
does not apply here). See examples/behavior-token.sh
.
- Response:
{ "token": "_act-jwt-..." }
— the JWT is the field, not
. (Behavior tokens carry no , unlike the WebSDK access-token endpoint.)
- TTL defaults to 1800s. Mint on demand per session; don't cache globally.
Wire it as an auth-gated route on your backend (e.g.
) that
generates/looks up the
, signs, calls Sumsub, and returns the response
to the browser.
Stage 2 — Frontend: capture the device with Fisherman
bash
npm i @sumsub/fisherman --save
ts
// Shape only — the full impl (fail-open try/catch, React StrictMode teardown) is in
// examples/fisherman-vanilla.html and examples/fisherman-react.tsx.
import { init, destroy } from '@sumsub/fisherman'
// init once when the page / form mounts — getBehaviorToken() calls your Stage-1 route
const fisherman = await init({
token: await getBehaviorToken(),
baseUrl: 'https://api.sumsub.com', // region-specific API host
accessTokenUpdateHandler: getBehaviorToken, // refresh near expiry
onError: () => destroy(), // never let DI block your form
})
// on the user action (e.g. login submit) — gate, fresh deviceBindingId, then confirm server-side
if (fisherman?.isDeviceIntelligenceEnabled) {
const deviceBindingId = crypto.randomUUID()
await fisherman.fingerprint({ deviceBindingId /*, linkedId: userId */ })
// Fisherman posts the capture under the token's session — your Stage-3 confirm
// call needs only the same accessToken to tie this device to the event.
}
destroy() // re-init for the next action
Rules:
- Gate on
isDeviceIntelligenceEnabled
— skip silently if DI is
off for the workspace.
- A fresh per action. Correlation of the capture to the
confirm call is the token's , not this id — only the
migration path needs it echoed in the confirm body (Stage 3).
- DI must never block your real flow — wrap in try/catch, fail open.
- after the action; re- for the next one. Refresh tokens via
(or call
updateAccessToken(newToken)
manually).
Runnable starting points:
examples/fisherman-vanilla.html
,
examples/fisherman-react.tsx
.
Stage 3 — Backend: confirm the event with the captured device
Tell Sumsub the action happened; the captured device rides along via the token's
session. Pick the call that matches the action — all use your
standard App
Token + HMAC signing (sign
ts + "POST" + path + body
, exactly like every other
Sumsub API call), and additionally carry the
behavior access token as the
query param so Sumsub ties the captured device to the event. The
does
not replace signing — it rides alongside it:
| Action | Endpoint | Doc |
|---|
| Login / signup / password reset / 2FA | POST /resources/applicants/-/kyt/txns/-/data?accessToken=…
| platform event |
| Financial transaction | POST /resources/applicants/-/kyt/txns/-/data?accessToken=…
| financial txn |
| Pre-KYC signup (create the applicant) | create applicant with + + | pre-KYC check |
How the device links: the confirm call is App-Token + HMAC signed
and
carries the
same behavior access token as
. Sumsub correlates
the captured fingerprint to the event
server-side using the session the token
was minted with (it reconstructs the key from your
; the token
scopes the session). You do
not put
or a device block in the
body — just identify the user + the event:
json
{
"txnId": "<unique-id-you-generate>",
"type": "userPlatformEvent",
"applicant": { "externalUserId": "<your-user-id>", "type": "individual" },
"userPlatformEventInfo": { "type": "login" }
}
Runnable:
examples/submit-platform-event.sh
.
Financial-transaction and pre-KYC bodies + full field lists:
references/standalone-flow.md
.
-based linking (Sumsub is migrating to it) is the exception:
mint the token with a
, pass the same one to
,
and send it at
applicant.device.deviceBindingId
. The default flow keys on
and needs none of that.
For
pre-KYC, also add
to the applicant if
you want Advanced IP verification, then request the applicant check to trigger
the DI analysis.
Stage 4 — Read the results
- Dashboard: Transactions and Travel Rule → Transactions, filter by
type User platform event → open the row → device details + risk labels.
- Webhooks: subscribe to the transaction/event reviewed webhooks to gate
programmatically (same signature-verification rules as the WebSDK skill —
verify on raw bytes).
- Device risk labels + scoring are the same catalog as the WebSDK path:
references/standalone-flow.md
. Full signal
definitions are in the sumsub-integrate-dint-websdk
skill's
references/device-signals.md
(install that skill for the file).
Decide which device labels are blocking (refuse / step-up) vs informational
(log) — that policy is yours.
Sandbox testing
- Sandbox has no real device signals; Fingerprint is simulated. Use the
() option on to force specific
signals for deterministic test outcomes.
- Run the full loop in sandbox (token → fingerprint → confirm) and confirm a
User platform event transaction appears in the dashboard.
Going live checklist
See also
sumsub-integrate-dint-websdk
— DI when
the page already embeds the Sumsub WebSDK widget.
- — the base
KYC WebSDK embed (token signing, webhook patterns reused here).
- — HMAC signing shared by every
Sumsub skill.
- — workspace validator (run
before Stage 1).
references/standalone-flow.md
— concepts,
event types, result reading, pre-KYC and financial-txn variants.
- Get started with Device Intelligence,
Generate access token,
— authoritative source if this skill drifts.