Skip to content

Latest commit

 

History

25 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

kyc

Unit E2E Coverage License: MIT

E2E covers backend, web, Android and the iOS simulator suite on every push, see Testing.

Your React Native or React web app renders one IdentityVerification component. The end user photographs an ID document, takes a selfie with a liveness check, and gets a verdict: approved, pending or declined. A VerificationSource picks one of three modes: a hosted page embedded in a hardened WebView or origin-pinned iframe, the native provider SDK in-process, or a proxy session on the reference backend. The two backend-backed modes go through the optional packages/kyc-service service, and every mode ends at Sumsub.

Embedded identity verification (KYC) for React Native and React web apps. One IdentityVerification component, one useIdentityVerification hook, provider-agnostic - Sumsub is the default provider and the only one implemented, and the app never learns its name. The end user photographs an ID document, takes a selfie with a liveness check, and your app gets approved, pending or declined.

The hard parts are the ones this library actually solves: camera and microphone permissions inside a WebView (the mediaCapturePermissionGrantType that WKWebView and Android both need), an origin-pinned postMessage channel instead of an open one, token refresh that survives a slow reviewer, and a webhook-backed status that a replayed callback cannot downgrade.

Status: v1 complete - both platform packages, the server package, the reference service, the demos and the full E2E suite. The first stable release is pending; every green push to main publishes a next prerelease (docs/releasing.md).

Mode What it is What your app installs Backend required
1. Hosted page A page speaking the
kyc-bridge protocol, embedded
in a hardened WebView or an
origin-pinned iframe
One package via the
Apollo-free /hosted
entry - no Apollo,
no GraphQL
The page (this
repo's packages/kyc-service,
or your own)
2. Native SDK The provider SDK runs in-process
(a React Native native module);
your app supplies an
access-token callback
kyc-react-native
(its /sumsub entry) +
the Sumsub SDK peer
Any backend that
mints provider
access tokens
(kyc-node does;
examples/access-token-demo
shows one mutation)
3. Proxy session Full orchestration: session
creation, token refresh,
webhook status sync,
status query
The package +
@apollo/client +
graphql
This repo's backend
service (packages/kyc-service)

Which mode? Hosted if you can serve (or point at) a page - it is the smallest install and the same on both platforms. Native SDK if the camera UX on a phone is what matters and your API can mint a provider token. Proxy if you want this repo's backend to own the session lifecycle and the webhook-backed status. The Apollo wiring and the GraphQL backend exist for mode 3 only; nothing of it ships with modes 1 and 2.

Reading path: Try it out (five minutes, no account) → Integration for your mode → the package README for your platform (RN, web) → Backend options → Deploy and operate.

Try it out

Everything runs locally against the mock provider: no Sumsub account, no credentials, a fake ID flow that ends in approved or declined.

git clone https://github.com/blinkbitcoin/kyc && cd kyc
make install                              # npm ci across all workspaces (installs git hooks)
direnv allow . && direnv allow packages/kyc-service   # once per machine: env + the nix dev shell
make db-up migrate backend                # dev Postgres, migrations, the reference service on :5100

Then, in a second terminal, one of:

make web                                  # the web demo on :5101 (hosted mode; VITE_KYC_MODE=proxy for mode 3)
make start && make ios                    # the React Native demo (or: make android)

Press Verify identity, walk the mock page, and watch the status land in the demo. KYC_MODE / VITE_KYC_MODE in the root .env switch the demo between modes (.env.example); KYC_MODE=fake-native drives the native-SDK launch branch with no SDK installed.

Against real Sumsub (sandbox), one command each:

make sumsub-env APP_TOKEN=… SECRET_KEY=… WEBHOOK_SECRET=…   # writes the service's .env (mode 600)
make sumsub-check                         # app-token auth + the level; mints a throwaway token
make e2e-live                             # the API tier: token, status, hosted page, a signed webhook
make live-web                             # the web demo on a public URL against the sandbox
make live-android                         # the RN demo on the attached phone (make live-ios for iPhone)

Dashboard setup, the level to create and the manual device checklist: docs/integration/sumsub.md.

Who are you? Three paths through this repository:

You are Your path
App developer /
integrator
Integration - the three modes, same component
consuming.md - registry setup and the minimal install
error-codes.md - every onError code and the host reaction
Backend
developer
The access-token preset - one endpoint in your own API
examples/access-token-demo - a runnable API that mints
Runbook: backend developer - what to build once
DevOps
engineer
Backend options - the two tiers, side by side
Deploy table - the copy-paste per target
Runbook: DevOps - env, secrets, boot guard, health

Integration

Every mode drives the same component with the same callbacks - the only thing that changes is the VerificationSource you pass in:

<IdentityVerification source={source} onComplete={…} onError={…} onCancel={…} />

The packages publish to GitHub Packages under the blinkbitcoin org - registry setup and the --omit=peer note: docs/integration/consuming.md. The modes below go from simplest to most capable. Start with the first one that covers your needs.

1. Hosted page - the simplest (no Apollo, no provider SDK)

Use when: you have, or can serve, a page that speaks the bridge protocol. This repo's packages/kyc-service serves one at GET /hosted/:sessionId, and the whole contract a page must honour is three bullet points.

npm i @blinkbitcoin/kyc-react-native react-native-webview @react-native-community/netinfo
# note: no @apollo/client, no graphql - the /hosted entry never reaches them
import { createHostedSource, IdentityVerification } from '@blinkbitcoin/kyc-react-native/hosted';

const source = createHostedSource({
  getSession: async () => yourApi.startVerification(),   // -> { url }
  refreshToken: async (s) => yourApi.refreshToken(s.sessionId),   // optional
});

<IdentityVerification
  source={source}
  onComplete={(result) => console.log(result.status)}
  onError={(error) => console.warn(error.code, error.message)}
  onCancel={() => navigation.goBack()}
/>;

The WebView is hardened in one place (mediaCapturePermissionGrantType: 'grant', an origin allow-list, a navigation guard, no popups, no file access), and the allowedOrigin the messages are pinned to is derived from the page url when you do not supply one. Details, and the three things a web host page must allow before a browser hands the frame a camera: docs/integration/hosted.md.

2. Native SDK - the best camera UX (one backend endpoint)

Use when: you are on mobile and want the provider's own full-screen capture and liveness flow rather than a WebView. The provider SDK needs an access token, and minting one needs a provider app token, which must never sit in a mobile bundle - so this mode needs one authenticated endpoint on your backend.

npm i @blinkbitcoin/kyc-react-native @sumsub/react-native-mobilesdk-module
cd ios && bundle exec pod install
import { IdentityVerification } from '@blinkbitcoin/kyc-react-native';
import { createSumsubNativeSource } from '@blinkbitcoin/kyc-react-native/sumsub';

const source = createSumsubNativeSource({
  getAccessToken: async () => (await yourApi.startVerification()).accessToken,
});

No WebView is rendered: the component detects isLaunchable(source) and hands the screen to the SDK. getAccessToken doubles as the SDK's own expiration handler, so there is nothing to refresh. Without the SDK peer installed the source fails closed with SDK_UNAVAILABLE rather than crashing - and createFakeLaunchableSource from @blinkbitcoin/kyc-core/testing lets you drive the whole launch branch in tests and E2E with no credentials at all. docs/integration/native-sdk.md.

3. Proxy session - full orchestration (this repo's backend service)

Use when: you want sessions, token refresh, provider webhooks and an authoritative status handled for you, and you are willing to run packages/kyc-service.

npm i @blinkbitcoin/kyc-react-native @apollo/client graphql
  1. Run the service and point it at your provider credentials:
make db-up migrate backend      # dev Postgres, migrations, server on :5100
  1. Register https://your-api/webhook/kyc/sumsub in the provider dashboard.
  2. Wire the client:
import {
  createKycApolloClient,
  createProxySource,
  IdentityVerification,
} from '@blinkbitcoin/kyc-react-native';

const client = createKycApolloClient({ uri: API_URL, getAuthToken });
const source = createProxySource({ client, platform: 'IOS' });

The backend persists one VerificationSession row per attempt with an audit trail, and its terminal-state guard means a replayed webhook can never downgrade an approved user. docs/integration/proxy.md.

Web apps

Everything above applies to @blinkbitcoin/kyc-react, with an origin-pinned <iframe> in place of the WebView and navigator.onLine in place of NetInfo, and the same Apollo-free /hosted entry, so a host that ships both platforms writes the same import on both. Start at packages/kyc-react/README.md and docs/integration/hosted.md.

The UI: component or hook

Independent of the mode above, pick how much of the screen the library draws. Same state machine in every column; the host takes over more from left to right.

Default Themed Headless
Drop in the component Recolor and relabel it Bring your own UI
<IdentityVerification source={source} … /> theme · styles · labels on the component useIdentityVerification + your own WebView / iframe

Every string the component renders is a labels key and every color a theme key, so a branded, multilingual host never shows the built-in copy. Details and code for each path: the package READMEs (RN, web).

Backend options

Every mode needs one server-side call - a provider access token minted for a user your backend has authenticated - and there are exactly two tiers to choose between. The app code is identical for both: the same VerificationSource calls one endpoint and uses what it gets back.

Tier What you run What your API must provide Capabilities Copy-paste
In-process
@blinkbitcoin/kyc-node
The package inside
your own Node API
(router or Fetch
handler)
Your own session check
(authenticate), and the
level from the levelFor
hook - which can refuse a
mint by throwing
Errors.validationError
Access tokens; the
session domain too,
over your own store
The access-token preset
Deployable
@blinkbitcoin/kyc-service
The package or the
ghcr.io/blinkbitcoin/kyc-service
image, as a function
or a container
SESSION_JWKS_URL or
SESSION_HS256_SECRET
(who the caller is)
Tokens always on;
sessions, the hosted
page, webhooks and
GraphQL with
DATABASE_URL
Deploy table

Mode 2 needs no database with the service: access tokens are always on, and DATABASE_URL only adds the sessions half (modes 1 and 3). Deploy targets are a Node container, Vercel, a Cloudflare Worker (tokens only), Kubernetes, or Lambda via the same image - one row each, with the commands, in the service's Deploy table.

Taking either tier live - Sumsub go-live, the environment, the secrets per platform, the boot guard and the verification checklist - is the runbook: docs/operations/production.md.

Testing

Coverage is enforced at 100% on all five packages and scripts/lib, with an 80% floor on the demos, and the E2E suites run the real mock provider page on every platform.

Tier Command What runs
Unit make test Every suite + lint + typecheck + format check;
no backend, no database
Coverage make coverage The same with thresholds; HTML report in
coverage/report/index.html
Backend E2E make e2e-backend Vitest against a dockerized Postgres:
DB up → migrate → tests → teardown
Server shapes make e2e-server-demos Boots the access-token example on the mock
provider and calls its mutation
Web E2E make e2e-web
make e2e-web-proxy
Playwright, hosted / proxy; builds the libraries
and bundles the demo against their dist
Mobile E2E make e2e-backend-up, then
make e2e-android / make e2e-ios
Maestro against the running stack
(make e2e-fake-native for the SDK launch branch)
Live Sumsub make test-live, make e2e-live The sandbox API tier; skips itself without
credentials (sumsub.md)
Static make check-ci, make docs-check,
make codegen-check, make codeql
actionlint + shellcheck, docs freshness and table
width, schema drift, CodeQL (local only)

CI is one pipeline per branch (ci.yml): Checks (changes, code, commits, docs) → Unit → E2E (backend, server demos, build packages, web, Android, iOS) → Badges, then Publish → Verify on main. iOS always runs; only the live Sumsub job is opt-in (E2E_LIVE=true or the e2e:live label - docs/operations/live-e2e-ci.md). Badges land in gh-pages/badges/<branch>/ and the coverage report is an artifact of every run. Ports are KYC_PORT_BASE (5100) plus an offset, so one variable moves a whole worktree (KYC_PORT_BASE=5300 make e2e-web).

Deploy and operate

The deployable is @blinkbitcoin/kyc-service: one Fetch core that runs as the ghcr.io/blinkbitcoin/kyc-service image, a Node process, a Vercel route or a Cloudflare Worker. Access tokens are always on; DATABASE_URL adds the sessions half (the hosted page, the webhook, GraphQL over Postgres).

docker run --rm -p 5100:5100 --env-file packages/kyc-service/.env ghcr.io/blinkbitcoin/kyc-service
docker run --rm --env-file packages/kyc-service/.env ghcr.io/blinkbitcoin/kyc-service node dist/node.js migrate

The service refuses to start on a bad environment: a session secret (SESSION_HS256_SECRET or SESSION_JWKS_URL), an absolute PUBLIC_BASE_URL and SUMSUB_WEBHOOK_SECRET once sessions are on, SUMSUB_APP_TOKEN and SUMSUB_SECRET_KEY with KYC_PROVIDER=sumsub; KYC_ENV=production refuses the mock and a sandbox token unless KYC_ALLOW_DEMO=true, and ALLOW_INSECURE_DEV=true belongs in a developer's .env, never in production. Register <PUBLIC_BASE_URL>/webhook/kyc/sumsub in the Sumsub dashboard, probe GET /health (it reports the capabilities that are on), and let the proxy leave Permissions-Policy and frame-ancestors on /hosted/* alone. One row per target with the commands: the service's Deploy table. The full story - the two tiers, Sumsub go-live, the environment, the secrets per platform, the boot guard, the checklist and the failure modes: docs/operations/production.md. The controls the service enforces: docs/architecture/security.md.

Repository Layout

Ordered by how likely you are to need each part:

Path What lives there
packages/kyc-react-native/ The React Native library you install:
IdentityVerification, useIdentityVerification, the
hardened hosted WebView, and the Sumsub
native-SDK source on its /sumsub entry.
packages/kyc-react/ The same pair for React web, over an
origin-pinned iframe.
packages/kyc-node/ The server half a backend installs: Sumsub
token minting and webhook verification, the
session domain, the hosted page, an Express
router and a Knex store. This repo's
backend is built on it.
packages/kyc-core/ The shared core both libraries build on:
VerificationSource, the capability guards,
the bridge protocol, the state machine, the
error-code contract, and the Sumsub mapping
on /sumsub. It arrives as a dependency -
you never install it directly.
packages/kyc-service/ The deployable on kyc-node: one Fetch
core, access tokens always, sessions
(Apollo + Postgres) with a database; a
container, a Node process, a Vercel route
or a Worker. Needed for mode 3 only; the
backend every E2E suite runs against.
examples/access-token-demo/ The other server shape: an existing GraphQL
API adds one mutation that mints a provider
access token for the native SDK (mode 2).
examples/serverless-handler-demo/ Server shape for route handlers and edge
functions: the package's access-token preset,
Request → Response.
examples/react-native-demo/ The React Native host: every KYC_MODE,
the themed variant, the Maestro suite.
examples/react-demo/ The web host: both VITE_KYC_MODEs, the
themed variant, the Playwright suites on
per-worktree ports.
scripts/ The tooling workspace the Makefile and
CI run: ci/, e2e/, release/, with
the logic in lib/*.mjs at 100% coverage.
docs/ How everything currently works - start at
docs/index.md; the runbook in
docs/operations/;
the rules behind the layout in
principles.md.

Development

Only needed if you are working on the packages themselves - consuming them requires none of this. Setup is the first block of Try it out.

make test                                # unit suites + lint + typecheck + format check
make coverage                            # 100% on the five packages and scripts/lib
npm test -w @blinkbitcoin/kyc-react -- useIdentityVerification    # one suite

make help lists every target (thin wrappers over the npm workspace scripts):

Target Purpose
make test
make coverage
Unit suites + lint + typecheck + format check / coverage thresholds
make build Build the four libraries (npm run check:packages for publint +
arethetypeswrong on the dist)
make codegen
make codegen-check
Emit schema.graphql and regenerate the client types / fail on drift
make diagrams
make diagrams-check
Render docs/diagrams/dist/*.svg and reassemble the page / fail on drift
make docs-check Warn on architecture changes without docs; fail on a stale diagram SVG
or a README table cell over 72 characters
make e2e-* The suites in Testing
make version
make release
What CI would publish / merge the release PR release-please
maintains (docs/releasing.md)

See docs/development-guide.md for the full workflow, environment variables and troubleshooting.

Documentation and contributing

About

Embedded identity verification (KYC) for React and React Native apps.

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages