Skip to main content

Adhere Web SDK

The Adhere Web SDK enables you to rapidly and securely verify user identities and perform facial liveness checks directly in your web applications. The SDK mounts a drop-in widget over your application, handling document capture, identity verification, and liveness detection — all in one seamless flow.

Features

  • Drop-in UI Modal — Responsive, animated widget that overlays your app via SmartComplyFlow.open().
  • CDN & npm Support — Install via npm/yarn or load directly from a CDN with zero build steps.
  • Dynamic Routing — Automatically adapts document and verification requirements from your dashboard configuration.
  • Single-Use SessionsclientId is your permanent integration key (from your SDK Config) and is reused for every session. Each createSession() call issues a fresh, single-use session token — that token, not the clientId, is what’s scoped to one verification.
  • Multi-Country Identity — Supports data and document channels across Nigeria (BVN, NIN, passports, driver’s licenses, voter’s cards, and more), Ghana, Kenya, South Africa, and Cote d’Ivoire, with generic passport verification available for other countries and country-agnostic liveness/face-match checks — exact channels depend on what’s enabled in your dashboard.
  • Two-Sided Document Capture — Front is always required; back is required, optional, or not offered depending on the document type (e.g. NIN is optional-back, since not every physical NIN document has a usable reverse side).
  • Hardware Agnostic Liveness — Uses native webcam and MediaRecorder API for cross-platform compatibility. A single passive scan (blink + natural head movement) — no discrete step-by-step prompts.

Installation

Add the script tag to your HTML:
The SDK is available globally as window.SmartComplySDK:
@1 always resolves to the latest 1.x.x release — bug fixes and new features reach your site automatically the moment we publish them, with no code change on your end, ever. We commit to never shipping a breaking change as a 1.x release; if a breaking change is ever needed, it ships as 2.0.0, and @1 keeps serving the last safe 1.x release until you deliberately opt in. This is the same versioning model used by most public JS SDKs (Stripe.js, Google Maps, etc.).
Other CDN options:
@1/@latest re-resolve on every page load — that’s what makes them self-updating, with no rebuild or redeploy needed on your side. An exact @X.Y.Z pin never moves until you manually change the number in your script tag. See npmjs.com/package/smartcomply-web-sdk for release history.

Option 2 — npm / yarn

Unlike the CDN, npm has no auto-updating option — this is true for every npm package, not specific to ours. npm install resolves to the latest version at the moment you run it, then locks that exact version in package-lock.json (or yarn.lock); it will not change again on its own. Run npm update smartcomply-web-sdk periodically (or before each deploy) to pick up new fixes — this stays within the ^1.0.x range already set in your package.json, and we commit to never shipping a breaking change within 1.x, so it’s always safe to run. Check npmjs.com/package/smartcomply-web-sdk for the current version number.

The easiest way to integrate is the drop-in widget. It handles the complete verification flow automatically.
Get your API Key and Client ID from your Adhere Dashboard — both come from your SDK Config and are permanent; reuse the same values for every session. What’s single-use is the session token the SDK obtains internally via createSession() (30-minute expiry, revoked after submission) — you never see or manage that token directly through the drop-in widget.The widget warns the user 2 minutes before this 30-minute expiry, then automatically shows a “session expired” message and closes itself (firing onClose) once the deadline actually passes — with a brief grace period if the user is mid-scan, so an active capture is never torn down mid-flow. If you need a fresh verification after that, call SmartComplyFlow.open() again to start a new session (available from smartcomply-web-sdk@1.0.76).

Configuration Parameters

sandbox is not currently available — use "production" for all integration and testing today. This section will be updated once sandbox is back.

Environment URLs


Branding

The SDK Config on your Dashboard supplies the brand name, description and theme colour that initializeConfig() returns, and the widget renders them.
The logo uploaded under Settings → Integrations → SDK → Brand Details is rendered by the Android and iOS SDKs only. The Web SDK does not show it yet, so a config used by both carries your mark on mobile and the default on web.

Framework Examples

React

Vue

Plain HTML (CDN)


Headless API (Advanced)

For full control over the UI, use the SmartComply class directly without the modal.
The liveness scan itself has its own 12-second capture window, separate from the 30-minute session TTL above. This timer starts only when active recording begins — right after the camera has centered on the user’s face and briefly re-stabilized (autofocus/exposure) — not from when the camera first opens. From that point, the widget shows a countdown ring around the face oval; the scan must detect the passive liveness signal (blink + natural head movement) within those 12 seconds or it fails with a “Time’s Up” screen and a Try Again button that restarts the scan using the same liveness entry (no re-charge).This timer is entirely local to the browser tab and only runs during the active scan — it is cleared the moment the scan resolves (success or timeout) and does not persist, run in the background, or resume if the user leaves and returns. In practice this means: if the user has already completed the liveness/face step and moved on to another part of the flow (e.g. reviewing their submission), the 12-second window is long finished and irrelevant — nothing about that step can time out again. The only clock still running at that point is the 30-minute session TTL, which governs the SDK’s API calls generally, not the completed scan.

onComplete Payload

onComplete fires as soon as the user finishes their part of the flow (the “Verification Submitted” screen renders) — it is a submission receipt, not a verification verdict. Backend processing (face match, document read, government DB check) continues after this fires, and status is always "processing" here regardless of the eventual outcome. The real pass/fail result only ever arrives via webhook.
verificationResult is only present for data verification (BVN/NIN) — it’s the immediate government-database lookup result confirming the ID number matched a real record. It says nothing about the face match, which is still pending. It’s absent for document verification flows.

Receiving Results (Webhook)

The backend delivers exactly one liveness.completed webhook per verification, to the URL configured in your SDK Config, once face matching (and OCR / government DB check, depending on the flow) has finished.
This is the SDK’s own webhook — configured per SDK Config and specific to liveness.completed. It’s separate from the platform-wide webhook system described in Webhooks (transaction monitoring, general KYC module events, {success, module, event, data} shape). Both currently sign with HMAC-SHA256 and a sha256=-prefixed header, hex-encoded — verify against the raw request body either way.

Payload shape

For document verification, the same top-level shape applies, with verification_type: "document_verification" and a document block (OCR fields + document-to-selfie face match) instead of customer_profile:
document also carries place_of_birth, place_of_issue, address, district, division, location, sub_location, serial_number, and barcode_numbernull unless the specific document type carries that field (e.g. serial/barcode numbers mainly apply to newer Kenyan ID cards). face_match.reason is populated with a user-facing explanation when verified is false or the match was skipped.
face_match.decision ("MATCH", "NO_MATCH", or "REJECTED") is what verified is actually derived from — read decision rather than comparing confidence_percentage against a threshold of your own, since our internal match threshold isn’t part of this payload and may change over time. "REJECTED" means liveness/anti-spoofing failed before any comparison ran, so confidence_percentage can be absent or 0 alongside verified: false for a reason unrelated to how similar the faces looked.
status: "passed" means the check ran to completion — not that the person matched. A face mismatch, low confidence score, or expired document still reports status: "passed", with the real outcome recorded in biometrics.face_match.verified (and document.is_expired for document verification). status: "failed" is reserved for cases where the check itself couldn’t run (service error, no selfie captured, government DB rejection). Never gate access on status alone — always check face_match.verified.
verification_id matches the entryId your onComplete callback received.

Verify the signature

The signature header is X-Adhere-Signature: sha256=<hex> — note the sha256= prefix. It’s computed over the exact compact-JSON bytes of the request body, so your handler must verify against the raw body, not a re-serialized copy of the parsed JSON (re-stringifying can produce different bytes and the signature will never match).

Security Notes

  • Client ID — Permanent, from your SDK Config. Reuse the same clientId for every session — there’s no per-session ID to generate.
  • API Key — Never expose your API key in client-side code in production. Use environment variables.
  • Session Tokens — The single-use part. Automatically obtained and managed by the SDK per verification via createSession(), expire after 30 minutes, and are revoked immediately once liveness is submitted.

Troubleshooting