Skip to content
didit-protocolPublic

About

A lightweight Web SDK for identity verification.

Resources

Contributing

Stars

2 stars

Watchers

4 watching

Forks

Repository files navigation

DiditSDK for Web

A lightweight JavaScript/TypeScript SDK for embedding Didit identity verification in web applications.

Features

  • 🎯 Simple API - Singleton pattern with easy-to-use methods
  • 🔐 Flexible Integration - Use UniLink URL directly or create sessions via backend
  • 💸 Transaction Submission - Submit monitored transactions (incl. Travel Rule) with a scoped token, automatic device intelligence, and an auto-launched wallet-ownership flow
  • 🛡️ Event Monitoring - Send sign-ups, logins, failed logins and other end-user events to Didit in batches, tied to the same person as their verifications and transactions
  • 📱 Responsive - Works on desktop and mobile browsers
  • 🎨 Customizable - Configuration options for styling and behavior
  • 📦 Multiple Formats - ESM, CommonJS, and UMD builds
  • 🔷 TypeScript - Full type definitions included

Authentication packages

This repository also holds Didit Authentication for web apps:

Installation

NPM/Yarn

npm install @didit-protocol/sdk-web
# or
yarn add @didit-protocol/sdk-web

CDN (UMD)

<script src="https://unpkg.com/@didit-protocol/sdk-web/dist/didit-sdk.umd.min.js"></script>

Event monitoring and device signals live in @didit-protocol/signals, a small package that @didit-protocol/sdk-web depends on and re-exports. Install it on its own when you only need events: npm install @didit-protocol/signals.

Package Version

Inspect the installed SDK version at runtime:

import { SDK_VERSION } from '@didit-protocol/sdk-web';

console.log(SDK_VERSION);

SDK_VERSION is injected from package.json during the build. Release maintainers should update the package with npm version <version> --no-git-tag-version, then run npm run test:build-version. That check validates ESM, CommonJS, UMD, minified UMD, and the emitted TypeScript declaration against both the current version and a simulated future version.

Quick Start

ES Modules / TypeScript

import { DiditSdk } from '@didit-protocol/sdk-web';

// Set up completion callback
DiditSdk.shared.onComplete = (result) => {
  switch (result.type) {
    case 'completed':
      console.log('Verification completed!');
      console.log('Session ID:', result.session?.sessionId);
      console.log('Status:', result.session?.status);
      break;
    case 'cancelled':
      console.log('User cancelled verification');
      break;
    case 'failed':
      console.error('Verification failed:', result.error?.message);
      break;
  }
};

// Start verification with URL (from your backend or UniLink)
DiditSdk.shared.startVerification({
  url: 'https://verify.didit.me/session/session-token'
});

Script Tag (UMD)

<script src="https://unpkg.com/@didit-protocol/sdk-web/dist/didit-sdk.umd.min.js"></script>
<script>
  const { DiditSdk } = DiditSDK;

  DiditSdk.shared.onComplete = (result) => {
    if (result.type === 'completed') {
      alert('Verification completed: ' + result.session.status);
    }
  };

  function startVerification() {
    DiditSdk.shared.startVerification({
      // You can get this link by clicking on "copy link" in the workflows view
      url: 'https://verify.didit.me/u/WORKFLOW_ID_IN_BASE_64'
    });
  }
</script>

<button onclick="startVerification()">Verify Identity</button>

Integration Methods

There are two ways to integrate the SDK:

Method 1: UniLink URL (Simplest)

Use your workflow's UniLink URL directly from the Didit Console. No backend required.

DiditSdk.shared.startVerification({
  // You can get this link by clicking on "copy link" in the workflows view
  url: 'https://verify.didit.me/u/WORKFLOW_ID_IN_BASE_64'
});

Get your UniLink URL from the Didit Console → Your Workflow → Copy Link.

The session_id generated will be sent to you by an event. Check event reference here

Method 2: Backend Session (Recommended for production)

Your backend creates a session via the Didit API and returns the verification URL. This gives you more control over session creation, user tracking, and security. Read more about how the create session API works here: https://docs.didit.me/reference/create-session-verification-sessions

// Get the verification URL from your backend
const { url } = await yourBackend.createVerificationSession();

DiditSdk.shared.startVerification({
  url,
  configuration: {
    loggingEnabled: true
  }
});

Configuration

interface DiditSdkConfiguration {
  /**
   * Enable SDK logging for debugging
   * @default false
   */
  loggingEnabled?: boolean;

  /**
   * Custom container element to mount the modal
   * @default document.body
   */
  containerElement?: HTMLElement;

  /**
   * Z-index for the modal overlay
   * @default 9999
   */
  zIndex?: number;

  /**
   * Show close button on modal
   * @default true
   */
  showCloseButton?: boolean;

  /**
   * Show exit confirmation dialog when closing
   * @default true
   */
  showExitConfirmation?: boolean;  
  /**
   * Automatically close modal when verification completes
   * @default false
   */
  closeModalOnComplete?: boolean;

  /**
   * Render verification inline instead of modal overlay
   * @default false
   */
  embedded?: boolean;

  /**
   * Container element ID for embedded mode
   * Required when embedded is true
   */
  embeddedContainerId?: string;

  /**
   * Camera the document capture opens first: "front" or "back"
   * @default "back"
   */
  defaultDocumentCamera?: CameraLens;

  /**
   * Camera the face (liveness) capture opens first: "front" or "back"
   * Applies to passive liveness; devices without a rear camera keep the front one
   * @default "front"
   */
  defaultLivenessCamera?: CameraLens;

  /**
   * Show the in-capture camera switcher on the document step
   * false locks the user to defaultDocumentCamera
   * @default true
   */
  showDocumentCameraSwitchButton?: boolean;

  /**
   * Show the in-capture camera switcher on the liveness step
   * false locks the user to defaultLivenessCamera
   * @default true
   */
  showLivenessCameraSwitchButton?: boolean;
}

type CameraLens = "front" | "back";

Camera Options

The same four camera options the native SDKs expose. By default the document capture opens the rear camera and the face capture opens the front (selfie) camera, and both steps offer an in-capture camera switcher on devices with more than one camera.

Option Type Default Description
defaultDocumentCamera CameraLens 'back' Lens used when first entering the document capture screen
defaultLivenessCamera CameraLens 'front' Lens used when first entering the liveness (passive face) capture screen
showDocumentCameraSwitchButton boolean true Show the in-capture camera switcher on the document screen; false locks the user to defaultDocumentCamera
showLivenessCameraSwitchButton boolean true Show the in-capture camera switcher on the liveness screen; false locks the user to defaultLivenessCamera
// A kiosk: the operator points the device at the person, on the rear camera, with no way to switch
DiditSdk.shared.startVerification({
  url,
  configuration: {
    defaultLivenessCamera: 'back',
    showLivenessCameraSwitchButton: false
  }
});
  • CameraLens is 'front' or 'back'; any other value is ignored, as is a non-boolean switcher flag.
  • A device without the requested camera keeps the one it has (a laptop asked for 'back' still uses its webcam), and the switcher is hidden on single-camera devices regardless of the flags.
  • The liveness options apply to the passive liveness check. Active liveness is not affected.
  • The SDK forwards the options to the verification page as query parameters of the verification URL: document_camera, liveness_camera, document_camera_switch and liveness_camera_switch. If you open the verification URL directly instead of through the SDK, set them on the URL yourself (with & when the URL already has a query string, or through the URL API's searchParams).

Modal Sizing

Modal mode uses a maximum width of 500px and a maximum height of 700px. On shorter desktop and landscape viewports, the iframe tracks 90% of the visible viewport so the modal never extends beyond its container. The hosted flow owns the scrollbar inside the iframe, keeping later steps reachable without scrolling the page behind the modal.

Browsers that support dynamic viewport units use dvh, while older supported browsers fall back to vh. At widths of 540px and below, the modal fills the visible viewport. The close button and exit confirmation stay inside the modal at every size.

Redesigned verification flow

When the hosted verification page reports its layout with a didit:layout message, the modal sizes itself around it:

Window width Modal
1024px and wider The handoff card: 960px by 660px in every state, at most 90% of the window's width and height
541px to 1023px 500px wide, as tall as the page's content, at most 90% of the window's height
540px and below Full screen, as above

The close button becomes a 44px circle over the page's top bar. It follows the page's light or dark theme and moves to the left for right-to-left languages. If a large text size makes the exit confirmation taller than a short page, the confirmation scrolls inside the modal. Until the page reports its layout, and for pages that never do, the modal keeps the 500px by 700px size above. Embedded mode keeps filling its host element. There is nothing to configure, and every existing option keeps working.

The SDK tells the page how it is shown with two query parameters on the verification URL: sdk_presentation (modal or embedded) and, in a modal, sdk_close_button (true or false, following showCloseButton). The SDK handles the didit:layout message itself and does not pass it to onEvent.

Embedded Mode

Render verification inline instead of a modal overlay. Embedded mode does not apply the modal's 500px by 700px cap; the SDK fills the host element's content box. Give the host an explicit or otherwise resolved height:

<div id="verification-container" style="width: 500px; height: 700px;"></div>
DiditSdk.shared.startVerification({
  url: 'https://verify.didit.me/u/...',
  configuration: {
    embedded: true,
    embeddedContainerId: 'verification-container'
  }
});

If the host element has no resolved height, the embedded iframe also has no usable height. Resize the host element when your layout changes; the SDK continues to fill it automatically.

If embeddedContainerId is missing or no element carries it, the launch fails with the failed result (and the error state) instead of silently rendering nothing: embedded mode never falls back to the modal overlay.

Verification Results

The SDK returns three types of results:

Completed

Verification flow finished (approved, pending, or declined).

{
  type: 'completed',
  session: {
    sessionId: 'session-uuid',
    status: 'Approved' | 'Pending' | 'Declined'
  }
}

Cancelled

User closed the verification modal.

{
  type: 'cancelled',
  session: {
    sessionId: 'session-uuid',
    status: 'Pending'
  }
}

Failed

An error occurred during verification.

{
  type: 'failed',
  error: {
    type: 'sessionExpired' | 'networkError' | 'cameraAccessDenied' | 'unknown',
    message: 'Your verification session has expired.'
  }
}

State Management

You can observe the SDK state for custom UI:

DiditSdk.shared.onStateChange = (state, error) => {
  switch (state) {
    case 'idle':
      // Ready to start
      break;
    case 'loading':
      // Loading verification iframe
      showLoadingSpinner();
      break;
    case 'ready':
      // Verification in progress
      hideLoadingSpinner();
      break;
    case 'error':
      // Error occurred
      showError(error);
      break;
  }
};

// Check current state
console.log(DiditSdk.shared.state);

// Check if verification is presented
console.log(DiditSdk.shared.isPresented);

Transaction Submission

Submit monitored transactions — including Travel Rule transfers and crypto screening — straight from the browser. Your backend mints a short-lived scoped token with POST /v3/transactions/sdk-token/; the SDK does the rest: it binds the subject to the token server-side, attaches device intelligence automatically, and auto-launches the wallet-ownership flow when the transaction requires proof of wallet control.

import { DiditSdk, DiditTransactionError } from "@didit-protocol/sdk-web";

try {
  const result = await DiditSdk.shared.submitTransaction({
    transactionToken: sdkToken, // minted by YOUR backend - never ship an API key
    transaction: {
      txnId: "wd-2026-07-07-0042",
      category: "travelRule",
      details: {
        direction: "out",
        amount: "0.25",
        currency: "ETH",
        currencyType: "crypto",
        cryptoParams: { crypto_chain: "ETH" },
      },
      subject: { type: "individual", fullName: "Jane Doe" },
      counterparty: {
        type: "individual",
        fullName: "Ana Diaz",
        paymentMethod: { type: "unhosted_wallet", accountId: "0xBeneficiaryWallet01" },
      },
      travelRule: {
        beneficiaryData: { wallet_address: "0xBeneficiaryWallet01", name: "Ana Diaz" },
      },
      includeCryptoScreening: true,
    },
    autoLaunchAction: true, // default - auto-launches wallet_ownership in the SDK modal
    onActionCompleted: (refreshed) => {
      console.log(refreshed.status, refreshed.travelRuleStatus);
    },
  });

  console.log(result.transactionId, result.status);

  // verification_session actions are NEVER auto-launched - start them yourself:
  if (result.actionRequired?.type === "verification_session") {
    DiditSdk.shared.startVerification({ url: result.actionRequired.url });
  }
} catch (error) {
  if (error instanceof DiditTransactionError) {
    // error.type: "expired_token" | "invalid_token" | "validation" | "network"
    // error.fieldErrors carries per-field details for validation errors
  }
}

Key points:

  • transactionToken — scoped to one user (vendor_data) at mint time; the subject identity is enforced server-side from it, so a tampered client cannot submit for another user.
  • autoLaunchAction (default true) — auto-launches only wallet_ownership actions (in the SDK's overlay modal); a verification_session action always comes back in result.actionRequired for your app to launch with startVerification.
  • Device intelligence — a privacy-safe fingerprint is attached to every submission automatically; IP and user agent are derived server-side.
  • onActionCompleted — receives the refreshed transaction after an auto-launched flow completes or its window closes.

Full guide: SDK Transaction Submission.

Event Monitoring

Send what your end users do (sign-up, login, failed login, logout, password reset, MFA, device change) to Didit's event monitoring, where accepted events are associated with the same person as their verifications and transactions.

Your backend mints a short-lived monitoring token for the signed-in person with POST /v3/events/sdk-token/, then hands it to the page:

import { DiditSdk, DiditEventError, configureEvents, flush } from "@didit-protocol/sdk-web";

// Optional: hear about events Didit refused or the SDK had to drop.
configureEvents({
  onError: (error: DiditEventError) => console.warn(error.type, error.eventIds, error.errors),
});

DiditSdk.shared.submitEvent({
  monitoringToken, // minted by YOUR backend for this person - never ship an API key
  event: { category: "auth", actionType: "login", payload: { method: "password" } },
});

DiditSdk.shared.submitEvent({
  monitoringToken,
  event: { category: "auth", actionType: "login_failed", payload: { reason: "wrong_password" } },
});

// Before a redirect you control, send what is waiting and read the outcome of each event:
const { acceptedCount, rejectedCount, pendingCount, results } = await flush();

How events travel:

  • Batched to POST /v1/events/batch/ with the X-Monitoring-Token header: as soon as 25 events are waiting, after 5 seconds, and when the page is hidden or closed. Change the numbers with configureEvents({ batchSize, flushIntervalMs }).
  • The person comes from the token, never from the page: Didit binds every event to the vendor_data the token was minted for. Each event keeps the token it was submitted with, so events from before and after a sign-in never mix people.
  • Device intelligence is attached to every batch (the same fingerprint as transaction submissions); IP address, user agent and origin are taken server-side.
  • Leaving the page starts sending waiting events immediately with fetch and keepalive, which outlives the page (browsers allow 64 KiB per page for this). If fingerprint hashing is still pending, the request retains the persistent device id and collected signals but omits the unfinished hash. See the device-context tradeoff for stored events. sendBeacon is not used because it cannot carry the token header.
  • Safe retries: on a network error, HTTP timeout, rate limit or server error the same events are sent again after 10, 20 and 40 seconds, then every minute (or right away when you call flush()). An event Didit already stored comes back as replayed, never as a duplicate.
  • Refusals are final: an invalid, expired, revoked or used-up token (invalid_token, expired_token), event monitoring switched off for the application (monitoring_disabled) or an event that fails validation (validation) is reported through onError and in the flush() results, and is not sent again.
  • Queue pressure: the queue targets 1,000 waiting events (maxQueueSize). When the queue is full, page views and other low-value events are dropped first (dropped); security, payment, dispute, responsible-gambling and bet events are never dropped, so protected events can exceed that target.
Event field Description
category auth, session, security, payment, dispute, gameplay, bonus, affiliate, responsible_gambling or custom
actionType What happened, e.g. signup, login, login_failed, logout, password_reset, mfa_challenge
eventId Your idempotency key; a UUID is generated when omitted. submitEvent returns it
occurredAt Date or ISO 8601 string; defaults to now
payload Category-specific keys, e.g. payment_reference_id, amount, currency, payment_method and status for payments
session, transactionContext, customValues, subject, business, timeZone Optional context; subject only adds profile hints, the person always comes from the token

Delivery is best effort: the queue is in memory, and browsers can terminate pending requests. Await flush() and check pendingCount before a redirect you control. Browser delivery requires the API to allow X-Monitoring-Token and the fingerprint headers in its CORS policy.

Full guide: SDK Event Submission.

API Reference

DiditSdk.shared

The singleton SDK instance.

Methods

Method Description
startVerification(options) Start the verification flow
submitTransaction(options) Submit a monitored transaction with a scoped token (guide)
submitEvent(options) Queue an end-user event for event monitoring with a monitoring token; returns the event id (Event Monitoring)
close() Programmatically close the verification modal
destroy() Destroy the SDK instance and clean up

Properties

Property Type Description
state DiditSdkState Current SDK state
configuration DiditSdkConfiguration Current configuration
isPresented boolean Whether verification modal is open
errorMessage string Error message (when in error state)

Callbacks

Callback Parameters Description
onComplete (result: VerificationResult) Called when verification finishes
onStateChange (state: DiditSdkState, error?: string) Called on state changes
onEvent (event: VerificationEvent) Called on granular verification events

Granular Events

Track verification progress with the onEvent callback:

DiditSdk.shared.onEvent = (event) => {
  console.log('Event:', event.type, event.data);
};

Event Reference

Event Description Data Payload
didit:ready The page is ready and the sessionId is available { sessionId }
didit:started User started verification { sessionId? }
didit:step_started A verification step began { step, sessionId? }
didit:step_completed A step finished successfully { step, nextStep?, sessionId? }
didit:step_changed Current step changed { step, previousStep?, sessionId? }
didit:media_started Camera/media capture started { step, mediaType }
didit:media_captured Photo/video captured { step, isAuto }
didit:document_selected User selected document type and country { documentType, country }
didit:verification_submitted Data submitted for processing { step, sessionId? }
didit:code_sent OTP code sent { step, channel?, codeSize?, sessionId? }
didit:code_verified OTP code verified { step, sessionId? }
didit:status_updated Session status changed { status, previousStep?, sessionId? }
didit:completed Verification flow completed { sessionId?, status?, country?, documentType? }
didit:cancelled User cancelled verification { sessionId? }
didit:error An error occurred { error, step?, sessionId? }
didit:close_request User requested to close modal (no data)

Step Values

The step field can be one of:

  • document_selection - Document type selection
  • document_front - Front side of document
  • document_back - Back side of document
  • face - Face/liveness verification
  • email - Email verification
  • phone - Phone verification
  • poa - Proof of address
  • questionnaire - Questionnaire step

Channel Values

The channel field in code_sent can be:

  • email - Code sent via email
  • sms - Code sent via SMS
  • whatsapp - Code sent via WhatsApp

Code Size

The codeSize field in code_sent indicates the OTP code length (e.g., 4 or 6 digits).

Browser Support

  • Chrome 70+
  • Firefox 65+
  • Safari 12+
  • Edge 79+

Releasing

Each release starts from a tag pushed to a commit on master. The workflow checks that the tag matches the package versions and that each package's CHANGELOG.md has a section for that version, then creates the GitHub release and publishes to npm with trusted publishing.

Tag Publishes Workflow
signals-v<version> @didit-protocol/signals release-signals.yml
auth-v<version> @didit-protocol/auth, then @didit-protocol/auth-react, then @didit-protocol/auth-nextjs, all at the same version release-auth.yml
v<version> @didit-protocol/sdk-web release.yml

When versions depend on each other, release in this order and wait for each to reach npm: signals, then auth, then sdk-web. The auth and sdk-web workflows stop before publishing if the @didit-protocol/signals version they depend on is not on npm yet. @didit-protocol/auth-nextjs pins @didit-protocol/auth and @didit-protocol/auth-react at exactly its own version, so bump all three together; npm run test:release checks that before a tag does.

License

Copyright © 2026 Didit. All rights reserved.

About

A lightweight Web SDK for identity verification.

Resources

Contributing

Stars

2 stars

Watchers

4 watching

Forks

Releases

Packages

Used by

Contributors

Languages