npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

smartcomply-web-sdk

v1.0.75

Published

Drop-in identity verification (KYC) widget for web apps — face liveness, NIN/BVN, document verification

Readme

SmartComply Web SDK

Drop-in identity verification (KYC) widget for web applications. Embed one line of code and let your users verify their identity through facial liveness detection, government ID checks (BVN/NIN), and international document verification (passport, national ID, driver's license).

Overview

SmartComply SDK is a KYC-as-a-service widget that businesses embed in their web apps to verify end-user identities. It handles the full verification flow — from collecting ID details to live face matching — and delivers results asynchronously via webhook.

Target Audience

  • Fintechs, banks, and lending platforms that need to onboard customers securely
  • Any business required to perform Know Your Customer (KYC) checks before granting account access

What It Does

The SDK verifies that a person is real, alive, and who they claim to be:

  1. Identity verification — Validates the user's BVN/NIN against government databases, or extracts data from uploaded documents
  2. Liveness detection — A short passive camera scan (blink + natural head movement, one continuous window) to prove the person is physically present — not a photo or video replay
  3. Face matching — Compares the user's live selfie against:
    • The government-returned photo (BVN/NIN flow), or
    • The face on their uploaded document (passport/license flow)

Two Verification Flows

| Flow | How it works | ID Types | |------|-------------|----------| | Data verification | User enters an ID number → backend validates against government database → face match against government photo | BVN, NIN, and other data-only channels configured in your dashboard | | Document verification | User captures/uploads a document photo (front, plus back if required or offered) → data is extracted + expiry checked → face match against document photo | Passport, National ID (NIN slip/card), Driver's License, Voter's Card, and other document channels configured in your dashboard |

Exactly which ID types are available depends on what's enabled for your account and country in the Adhere dashboard — fetch config.channels (see Load Configuration) rather than hardcoding a list.

End-to-End Flow

┌─────────────────────────────────────────────────────────────────────┐
│  YOUR APP                                                           │
│  ┌───────────────────────────────────────────────────────────────┐  │
│  │  SmartComply Widget (modal)                                   │  │
│  │                                                               │  │
│  │  Welcome → Country → ID Type → Enter ID / Upload Doc          │  │
│  │      → Face Liveness (camera) → Done                          │  │
│  └───────────────────────────────────────────────────────────────┘  │
│                              │                                      │
│                    submit    │                                      │
│                              ▼                                      │
│                      Adhere Backend                                 │
│                         │                                           │
│               ┌─────────┼──────────┐                               │
│               ▼         ▼          ▼                                │
│          Face Match   Document Read   Gov DB Check                 │
│        (+ liveness)   (extraction)      (BVN/NIN)                  │
│               └─────────┼──────────┘                               │
│                         ▼                                           │
│                    Webhook POST ──────────► YOUR SERVER             │
│                    (signed with HMAC)       (result handler)        │
└─────────────────────────────────────────────────────────────────────┘

Install

Recommended: CDN with @1 (set once, stays current automatically)

<script src="https://cdn.jsdelivr.net/npm/smartcomply-web-sdk@1/dist/smartcomply.browser.js"></script>

<script>
  // SDK available as window.SmartComplySDK
  SmartComplySDK.SmartComplyFlow.open({
    apiKey: "your_api_key",
    clientId: "your_client_id",
    onComplete: (result) => console.log("Done:", result),
  });
</script>

@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 will keep 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:

<!-- Always the newest release, including any future major version — for
     prototyping, or if you specifically want every change instantly -->
<script src="https://cdn.jsdelivr.net/npm/smartcomply-web-sdk@latest/dist/smartcomply.browser.js"></script>

<!-- Pin to one exact version — only if you need manual control over
     updates. Replace X.Y.Z with the version you've tested against. -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/smartcomply.browser.js"></script>

@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.

Alternative: npm

npm install smartcomply-web-sdk

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; 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.


Option 1: Drop-in Widget (Recommended)

The fastest way to add KYC. Opens a complete, branded verification modal — handles everything automatically.

<button id="verify-btn">Verify Identity</button>

<script type="module">
  import { SmartComplyFlow } from "smartcomply-web-sdk";

  document.getElementById("verify-btn").onclick = () => {
    SmartComplyFlow.open({
      apiKey: "pk_live_your_api_key",
      clientId: "your-sdk-config-uuid",
      environment: "production",

      onComplete(result) {
        // Verification submitted! Final result arrives via webhook.
        console.log("Entry ID:", result.entryId);
        console.log("Status:", result.status);  // "processing"
        // Update your UI — tell the user to wait for confirmation
      },

      onError(err) {
        console.error("Verification failed:", err.message);
      },

      onClose() {
        // User closed the modal without completing
      },
    });
  };
</script>

That's it. The SDK will:

  1. Create a secure session with the Adhere backend
  2. Show a branded welcome screen (your brand name + theme from dashboard)
  3. Let the user select their country and ID type
  4. Collect their BVN/NIN number or capture their document photo (front, and back if the document has/needs one)
  5. Run a passive face liveness scan (camera stays open for a few seconds — the user blinks and turns their head naturally, no discrete step-by-step prompts)
  6. Record a short clip and submit everything to the backend
  7. Show a result screen and call your onComplete callback

What the User Sees

| Step | Screen | Description | |------|--------|-------------| | 1 | Welcome | Your brand name, description, and what to expect | | 2 | Country | Select country (auto-skipped if only one country configured) | | 3 | ID Type | Choose from the channels you configured (e.g., BVN, NIN, Passport) | | 4a | ID Input | Enter BVN/NIN number → instant backend verification against government DB | | 4b | Document | Capture/upload the document photo — front required; back is required, optional, or not offered depending on the document type (see Two-Sided Documents) | | 5 | Liveness | Camera opens — a single passive scan window; the user blinks and turns their head naturally, no sequential prompts | | 6 | Done | "Verification Submitted" — user clicks Done, final result arrives via webhook |

Two-Sided Documents

Some document types capture a back-side photo as well as the front:

| Document | Back side | |----------|-----------| | Passport | Never — front only | | NIN (slip or card) | Optional — offered, with a "Skip" option, since not every NIN document has a usable back | | Driver's License, Voter's Card, National ID Card | Required |

This is driven entirely by the requires_back_side flag the backend returns per channel in config.channels (see Load Configuration) — the drop-in widget handles it automatically. If you're building your own UI (headless), pass both document/id_file (front) and document_back (back) to liveness.create()/startCheck() when you have one; document_back is always optional at the API level regardless of what the UI requires.

SmartComplyFlow.open() Options

SmartComplyFlow.open({
  // Required
  apiKey: string,         // Your API key from the Adhere dashboard
  clientId: string,       // UUID from your SDK Config (created in dashboard)

  // Optional
  environment: "production",              // sandbox is not currently available — use "production"
  timeout: number,                        // Request timeout in ms (default: 30000)

  // Callbacks
  onComplete: (result) => void,   // Verification submitted successfully
  onError: (error) => void,       // Unrecoverable error
  onClose: () => void,            // User closed the modal
});

onComplete Result

onComplete fires as soon as the user finishes their part (the "Verification Submitted" screen renders) — not when verification is actually decided. Backend processing (face match, OCR, government DB check) continues after this fires; the pass/fail outcome only ever arrives via webhook.

{
  entryId: 42,                          // Use this to track the verification — matches
                                         // verification_id in the webhook payload
  sessionId: "da7623bd-9158-4b56-...",  // The session token used for this flow
  status: "processing",                 // Always "processing" — this is a submission
                                         // receipt, not a verification verdict
  submittedAt: "2026-04-13T...",        // ISO timestamp
  verificationResult: {                 // Only present for data verification (BVN/NIN) —
                                         // the immediate government-DB lookup result.
                                         // This confirms the ID number matched a real
                                         // record; it says nothing about the face match,
                                         // which is still pending at this point.
    status: "success",
    code: "VERIFICATION_COMPLETE",
    data: { first_name: "Amara", last_name: "Okafor", identity_check_id: 123, ... }
  }
}

Option 2: Headless (Custom UI)

For full control over the user interface, use the SDK's API methods directly.

Initialize

import { SmartComply } from "smartcomply-web-sdk";

const sdk = new SmartComply({
  apiKey: "pk_live_your_api_key",
  clientId: "your-sdk-config-uuid",
  environment: "production",
});

Create Session

Every verification flow starts with a session. Sessions last 30 minutes and are single-use (revoked after liveness submission).

const session = await sdk.createSession();
// session.token — used internally for all subsequent API calls
// session.expires_at — ISO timestamp

Load Configuration

Fetch your SDK config (brand name, theme, available ID types per country):

const config = await sdk.initializeConfig();

console.log(config.brand_name);        // "Your Company Name"
console.log(config.verification_type); // ["data_verification"]

// Channels are grouped by country with field definitions:
console.log(config.channels);
// {
//   "nigeria": [
//     { id: 8, name: "National Identity Number (NIN)", fields: [{ type: "input", label: "Identification Number" }], requires_back_side: "optional" },
//     { id: 2, name: "Bank Verification Number Advanced (BVN)", fields: [{ type: "input", label: "Bank Verification Number" }] },
//     { id: 5, name: "Driver's License", fields: [{ type: "upload", label: "Document" }], requires_back_side: true }
//   ]
// }

Use config.channels to build your own country/ID type selector. Each channel's fields array tells you what inputs to render.

For a document-verification channel, requires_back_side tells you whether to also collect a back-side photo: true (required), false/absent (never), or the string "optional" (offer it, but the user can skip — see Two-Sided Documents).

Verify Identity (Data Verification)

For BVN/NIN — validates against the government database:

const result = await sdk.onboarding.verify({
  identity_type_id: 8,                                    // Channel ID from config.channels
  fields: { identification_number: "12345678901" },       // Field keys from channel's fields definition
});

if (result.status === "success") {
  console.log(result.data.identity_check_id); // Save this — needed for face matching in liveness
  console.log(result.data.first_name);        // "Amara"
}

Liveness Check (with built-in camera UI)

Mount the SDK's liveness UI into any container element:

const container = document.getElementById("liveness-container");

const result = await sdk.liveness.startCheck(container, {
  identifier: "12345678901",          // The ID number entered by the user
  identifier_type: "NIN",
  country: "NG",
  document: documentFrontBlob,        // Optional: document front photo (document flow)
  document_back: documentBackBlob,    // Optional: document back photo, if the channel's
                                       // requires_back_side is true or "optional"
  identity_check: identityCheckId,    // Optional: from verify response (data flow)
}, ["BLINK", "TURN_HEAD"]);

console.log(result.status); // "processing"

The camera UI runs a single passive scan (the user blinks and turns their head naturally within one window) rather than stepping through discrete prompts. The actions array (3rd argument, defaults to ["BLINK", "TURN_HEAD"]) is sent to the backend as a descriptive tag — shown on your Adhere dashboard — not a live command sequence the UI enforces. The SDK handles camera access, face detection, video recording, and submission end to end.

Liveness Check (fully manual)

If you want to handle camera and recording yourself:

// 1. Create entry
const entry = await sdk.liveness.create({
  identifier: "A12345678",
  identifier_type: "Passport",
  country: "US",
  challenge_actions: ["BLINK", "TURN_HEAD"],  // Descriptive tag, not a live prompt sequence
  autoshot_file: selfieBlob,          // Captured selfie (JPEG/PNG, max 5MB)
  document: passportFrontBlob,        // Optional: document front photo
  document_back: passportBackBlob,    // Optional: document back photo (rarely needed —
                                       // most document types are front-only; see
                                       // Two-Sided Documents above)
  identity_check: identityCheckId,    // Optional: from verify response
});

// 2. Run your own camera/detection/recording UI
// ...

// 3. Submit the recorded video + snapshot
const result = await sdk.liveness.submit(entry.id, videoBlob, snapshotBlob);
// result.status === "processing"

After submission, the session is revoked. Each clientId can only complete one full verification.


Receiving Results (Webhook)

Verification is processed asynchronously. After the user submits, the backend runs face matching, OCR (for documents), and the government DB check (for BVN/NIN), then delivers one liveness.completed webhook to the URL configured in your SDK Config once everything is done.

The payload shape is the same for both verification types — verification_type tells you which one it was, and document is only present for document verification.

Webhook Payload — Data Verification (BVN/NIN)

POST https://your-server.com/webhook
Content-Type: application/json
X-Adhere-Signature: sha256=<hmac-sha256-hex>

{
  "event": "liveness.completed",
  "verification_id": 42,
  "verification_type": "data_verification",
  "status": "passed",
  "failure_reason": null,
  "timestamp": "2026-08-08T10:15:00.000Z",
  "subject": {
    "identifier": "12345678901",
    "identifier_type": "National Identity Number (NIN)",
    "country": "nigeria"
  },
  "biometrics": {
    "liveness_verified": true,
    "face_match": {
      "attempted": true,
      "verified": true,
      "confidence_percentage": 70.0
    },
    "selfie_url": "https://.../autoshot.jpg",
    "face_analysis": {
      "gender": "Female",
      "dominant_emotion": "neutral",
      "face_quality": {
        "face_detected": true,
        "face_confidence": 0.98,
        "blur_score": 142.3,
        "is_blurry": false
      }
    }
  },
  "activity": {
    "session_id": "da7623bd-9158-4b56-a9e4-4bccf3c0133f",
    "started_at": "2026-08-08T10:12:00.000Z",
    "submitted_at": "2026-08-08T10:14:30.000Z",
    "completed_at": "2026-08-08T10:15:00.000Z",
    "duration_seconds": 180
  },
  "request_context": {
    "ip": { "address": "102.67.1.66", "city": "Lagos", "country_code": "NG", "...": "..." },
    "device": { "user_agent": "Mozilla/5.0 ...", "type": "desktop", "os": "Windows", "...": "..." }
  },
  "customer_profile": {
    "first_name": "AMARA",
    "last_name": "OKAFOR",
    "other_name": null,
    "date_of_birth": "01-Jan-1997",
    "age": 29,
    "gender": "Female",
    "id_number": "12345678901",
    "serial_number": null,
    "occupation": null,
    "place_of_birth": null,
    "place_of_live": "...",
    "date_of_issue": null,
    "photo_url": null
  }
}

photo_url is always null here — the government-returned photo isn't exposed in the webhook (it's already used server-side for face matching; the biometrics.selfie_url field above is the user's own selfie, not the reference photo).

Webhook Payload — Document Verification (Passport/License/NIN card)

Same top-level shape, plus a document block instead of (or alongside) customer_profile:

{
  "event": "liveness.completed",
  "verification_id": 43,
  "verification_type": "document_verification",
  "status": "passed",
  "failure_reason": null,
  "timestamp": "2026-08-08T10:20:00.000Z",
  "subject": {
    "identifier": "A12345678",
    "identifier_type": "Passport",
    "country": "usa"
  },
  "biometrics": { "...": "same shape as the data-verification example above" },
  "activity": { "...": "same shape as the data-verification example above" },
  "request_context": { "...": "same shape as the data-verification example above" },
  "document": {
    "status": "verified",
    "document_type": "passport",
    "is_expired": false,
    "first_name": "AMARA",
    "last_name": "OKAFOR",
    "date_of_birth": "1997-01-01",
    "age": 29,
    "gender": "Female",
    "nationality": "NGA",
    "place_of_birth": "LAGOS",
    "document_number": "A12345678",
    "expiry_date": "2030-06-15",
    "issue_date": "2020-06-15",
    "issuing_authority": "...",
    "place_of_issue": null,
    "address": null,
    "district": null,
    "division": null,
    "location": null,
    "sub_location": null,
    "serial_number": null,
    "barcode_number": null,
    "document_url": "https://.../document.jpg",
    "document_back_url": null,
    "face_match": {
      "attempted": true,
      "verified": true,
      "confidence_percentage": 55.0,
      "threshold_percentage": 35.0,
      "reason": null,
      "selfie_url": "https://.../autoshot.jpg",
      "document_face_url": "https://.../document_face.jpg"
    }
  }
}

Most of these are extracted from the document and will be null if the document type doesn't carry that field (e.g. serial_number/barcode_number mainly apply to newer Kenyan ID cards) or OCR couldn't read it:

status, document_type, is_expired, first_name, last_name, date_of_birth, age, gender, nationality, place_of_birth, document_number, expiry_date, issue_date, issuing_authority, place_of_issue, address, district, division, location, sub_location, serial_number, barcode_number, document_url, document_back_url, face_match (attempted, verified, confidence_percentage, threshold_percentage, reason, selfie_url, document_face_url).

face_match.reason is populated when verified is false or the match was skipped — a user-facing explanation (e.g. "Face similarity is below the match threshold").

Field Notes

  • verification_id — matches the entryId your onComplete callback received when the user submitted.
  • status — unlike onComplete's status (always "processing"), this is terminal — but it means "the check ran to completion," not "the person matched." A face mismatch, low confidence score, or expired document does not set status to "failed" — it's still "passed" with the real outcome recorded deeper in the payload. status: "failed" is reserved for cases where the check itself couldn't run (service error, no selfie captured, government DB rejection). Always check biometrics.face_match.verified (and document.is_expired, for document verification) to know whether the person actually passed — never gate access on status alone.
  • biometrics.face_match — present for every verification type. attempted: false means face matching was skipped (e.g. a document type with no face, like a CAC certificate or NIN slip) — verified/confidence_percentage are null in that case, meaning "not applicable," not a failure. When attempted: true, verified: false is the real "face did not match" signal.
  • biometrics.face_analysis — supplementary selfie diagnostics (emotion, blur/quality), null/absent if analysis failed or no selfie was captured. gender here is sourced from the verified document/government record, not estimated from the selfie itself. There is no age estimate — age (where shown) is calculated from the document's date of birth, not the photo.
  • customer_profile — only present for data verification, sourced from the government DB response.
  • document — only present for document verification, sourced from OCR + the document-to-selfie face match. Check document.is_expired separately — an expired document can still have verified: true on the face match.
  • request_context — IP/device metadata captured at session start; useful for fraud signals (e.g. ip.country_matches_document).

Possible Statuses

| Status | Meaning | |--------|---------| | passed | The check ran to completion — inspect biometrics.face_match.verified (and document.is_expired for documents) for the real outcome | | failed | The check itself could not complete — service error, no selfie/document captured, or a government DB rejection. See failure_reason for a user-safe description |

Verify Signature

Always verify the webhook signature to prevent spoofing. Two details matter here: the header value is prefixed with sha256=, and the signature is computed over the exact compact-JSON bytes the server sent (no extra whitespace) — so you must use the raw request body, not a re-serialized copy of the parsed object, or the HMAC won't match.

const crypto = require("crypto");

// IMPORTANT: register this route with a raw-body parser (not express.json()),
// or capture the raw body via a verify callback — re-stringifying req.body
// after JSON.parse produces different bytes and the signature will never match.
app.post(
  "/webhook/smartcomply",
  express.json({
    verify: (req, res, buf) => { req.rawBody = buf; },
  }),
  (req, res) => {
    const header = req.headers["x-adhere-signature"] || "";
    const signature = header.replace(/^sha256=/, "");
    const secret = process.env.WEBHOOK_SECRET.replace(/-/g, "");
    const expected = crypto
      .createHmac("sha256", secret)
      .update(req.rawBody)
      .digest("hex");

    const isValid =
      signature.length === expected.length &&
      crypto.timingSafeEqual(Buffer.from(signature, "hex"), Buffer.from(expected, "hex"));

    if (!isValid) return res.status(401).send("Bad signature");

    const { event, verification_id, status, biometrics, document } = req.body;

    // status === "passed" only means the check ran to completion — it is
    // NOT the same as "the person is verified". Check the real outcome:
    const faceMatched = biometrics?.face_match?.attempted
      ? biometrics.face_match.verified === true
      : true; // not attempted (e.g. NIN slip, CAC) — nothing to fail here
    const documentOk = document ? document.is_expired === false : true;

    if (event === "liveness.completed" && status === "passed" && faceMatched && documentOk) {
      // ✓ User is verified — update your database
      markUserAsVerified(verification_id);
    } else if (event === "liveness.completed") {
      // Check completed but didn't pass verification (face mismatch, expired
      // document) — or status === "failed" (the check itself couldn't run).
      // Either way, do not treat this as a verified user.
      recordVerificationOutcome(verification_id, req.body);
    }

    res.json({ received: true });
  }
);

Challenge Actions

The built-in camera UI (SmartComplyFlow.open() and liveness.startCheck()) runs a single passive scan — the user blinks and turns their head naturally within one continuous window, with no discrete step-by-step prompts. There is no server-side per-action scoring; liveness is confirmed by the scan as a whole (blink + motion detected during the window), not by ticking off each action in challenge_actions individually.

The challenge_actions/actions array you pass to liveness.create() or startCheck() is sent to the backend as a descriptive tag only — it's what shows up in the "Challenge Actions" column on your Adhere dashboard for that verification. It does not change what the widget's camera UI actually asks the user to do.

| Action | Used to describe | |--------|-------------------| | BLINK | The user blinked during the scan | | TURN_HEAD | The user turned their head during the scan | | TURN_LEFT | Legacy — turned head left (older multi-step challenge design) | | TURN_RIGHT | Legacy — turned head right (older multi-step challenge design) | | OPEN_MOUTH | Legacy — opened mouth (older multi-step challenge design) |

Actions must be UPPERCASE. Default (and recommended) is ["BLINK", "TURN_HEAD"] — it's what the widget actually does and what gets tagged accurately.


Error Handling

import { SDKError, AuthError, NetworkError } from "smartcomply-web-sdk";

try {
  await sdk.createSession();
} catch (err) {
  if (err instanceof AuthError) {
    // 401: Invalid API key, expired session, or disabled branch
  } else if (err instanceof NetworkError) {
    // No internet, timeout, DNS failure
  } else if (err instanceof SDKError) {
    // Backend error: validation, insufficient balance, etc.
    console.log(err.statusCode);  // HTTP status
    console.log(err.errorCode);   // Machine-readable code
    console.log(err.errorData);   // Additional error details
  }
}

| Error Code | HTTP | Meaning | |------------|------|---------| | INVALID_API_KEY | 401 | Bad/missing apiKey, malformed Authorization header, or the branch isn't enabled for onboarding — only ever returned at session creation (createSession()), not after | | INVALID_SESSION | 401 | Session token missing, malformed, expired (30 min), or already revoked — every call after createSession() uses this, not INVALID_API_KEY. Fix: call createSession() again; apiKey/clientId are unchanged and reused as-is | | SDK_CONFIG_NOT_FOUND | 404 | Invalid clientId | | VALIDATION_ERROR | 400 | Missing or invalid fields | | RETRY_LIMIT_EXCEEDED | 429 | User exceeded the retry limit for confirmation/liveness attempts — they must restart with a new session | | INSUFFICIENT_BALANCE | 402 | Top up your wallet |


Theming

The widget automatically uses the theme from your SDK Config (set in the Adhere dashboard):

  • default — Clean blue on white
  • midnight_blue — Dark mode with blue accents
  • sunset_gold — Warm gold on cream
  • forest_emerald — Green on mint

No client-side theme configuration required.


Browser Support

Requires camera access (getUserMedia), video recording (MediaRecorder), and WebAssembly.

| Browser | Version | |---------|---------| | Chrome | 80+ | | Firefox | 75+ | | Safari | 14+ | | Edge | 80+ |


Prerequisites

  1. Adhere account — Sign up at the dashboard
  2. API Key — Format: pk_live_... (from dashboard → API Keys)
  3. SDK Config — Create in dashboard with your brand name, theme, verification types, channels, and webhook URL
  4. Client ID — The UUID shown on your SDK Config
  5. Funded wallet — Each verification deducts from your balance
  6. Webhook endpoint — A URL on your server to receive verification results

TypeScript Support

Full TypeScript definitions are included. Key types:

import type {
  FlowOptions,
  FlowResult,
  SDKConfig,
  SDKInitConfig,
  SessionResponse,
  VerifyIdentityResponse,
  LivenessCreateResponse,
  LivenessSubmitResponse,
  ChallengeAction,
  LivenessWebhookPayload,
  ApiResponse,
} from "smartcomply-web-sdk";

Quick Start (Test in 2 Minutes)

For production, always use npm install. Clone only for testing/contributing.

# Install from npm (production)
npm install smartcomply-web-sdk

# Or clone and build locally
git clone https://github.com/386konsult/smartcomply-web-sdk.git
cd smartcomply-web-sdk
npm install
npm run build:all

Testing

Type-check and build the SDK locally before integrating:

cd smartcomply-web-sdk
npm run typecheck
npm run build:all

To test the built widget in a browser, open demo/index.html with a local HTTP server and enter your Adhere API key and SDK client ID. The demo calls the real Adhere backend — sandbox is not currently available, so use production credentials.

  • API Key: From your Adhere dashboard
  • Client ID: From your SDK Config in the Adhere dashboard

License

ISC