SDK reference, Incode Web SDK 2 Reference / Use Cases

Digital ID: mDL Verification

Warning

This guide is specific to Web SDK 2.x. For Web SDK 1.x, you can find documentation here.

Do not attempt to upgrade or implement without support. Contact your Incode representative to check if you are a candidate for this upgrade.

Verify a user with a mobile driver's license (mDL) presented from a phone wallet (such as Apple Wallet, Google Wallet, or a state DMV app like CA DMV Wallet) instead of capturing a photo of a physical ID.

You can offer this in three ways:

  • In the Incode Hosted onboarding app, where the wallet option appears automatically on the ID chooser screen after it is enabled in your flow configuration.
  • In your own custom web UI, using the Web SDK 2 ID manager to select an available digital-ID method from your own button.
  • From your own wallet button, using renderWallet() or createDeviceWalletManager() without the ID Capture UI. See Standalone integration.

For the full list of supported wallets, schemes, and attributes across regions, see Supported Digital IDs.

How it works

For mDL verification, the user must have their ID saved to their phone wallet. This is called a verifiable digital credential. Verification of a user's digital credential follows the same onboarding session model as the physical id verification:

  1. Your application creates or opens an Incode onboarding session.
  2. The user chooses to verify with their digital ID.
  3. The browser launches the native wallet sheet with navigator.credentials.get().
  4. The user selects the credential and consents to share the requested attributes.
  5. The encrypted wallet response is sent to Incode Server for decryption and verification.
  6. Verification results are stored on the session and can be retrieved using the standard Incode result APIs.

The user's identity attributes are not returned directly to the web page from the wallet call. The wallet response is encrypted for Incode services and verified attributes are made available through the session's standard OCR data, score, and webhook result surfaces.

Supported Wallets

Platform Wallet Browser Path
iOS Apple Wallet Safari apple_web
iOS CA DMV Wallet Safari apple_web
Android Google Wallet Chrome google
Android CA DMV Wallet Chrome google

Incode supports CA DMV Wallet app on both platforms. The CA DMV Wallet app is presented through same path for iOS and Android as Apple and Google wallet

Apple requires approval for every individual client (relying party). Contact your Incode representative before going live with Apple Wallet.

Incode is an approved Google Verifier registrar and handles Google Wallet registration on your behalf. The user must still be on a supported Android Chrome environment with a compatible digital ID in Google Wallet. Contact your Incode representative before going live with Google Wallet.

Prerequisites

Before you go live

  • A wallet method enabled for your flow. Enable Apple Wallet, Google Wallet, or CA DMV Wallet with the Manage Digital IDs setting in ID Capture module configuration in Dashboard. The flow configuration then carries it under digitalIds.methods.
  • Wallet approval. Apple approves each relying party individually. For Google Wallet, Incode is a registered verifier and handles the registration for you. Contact your Incode Representative before going live with either wallet.
  • Registered domains. Apple Wallet presents only on website domains approved for your integration. Send your Incode Representative every domain that opens the wallet — production, staging, and test — and allow time for approval. The same applies when you embed <incode-flow> on your own domain.

Page requirements

  • An initialized session. Activate the session token before you start the wallet — setup({ apiURL, token }), or setup() followed by initializeSession() (see Headless Mode → Setup). The wallet request and the response submission are both session-scoped.
  • HTTPS. Browsers expose the Digital Credentials API only in secure contexts. On plain HTTP, the SDK reports UNSUPPORTED_PLATFORM.
  • A user gesture. Start the wallet from a click or tap handler. Browsers open the wallet sheet only during the gesture's transient user activation, which expires shortly after the tap. Each wallet request consumes it, so a retry needs a new tap.
  • A top-level page, or an iframe that allows the wallet. A cross-origin iframe can use the Digital Credentials API only when the embedding page grants it with <iframe src="…" allow="digital-credentials-get">. Without it, the browser rejects the wallet request.
  • The system browser. In-app WebViews often don't expose the Digital Credentials API, so the SDK reports UNSUPPORTED_PLATFORM there. In a native app, use the Incode iOS Apple Wallet or Android Google Wallet integration instead.

Option 1: Use the Incode Hosted onboarding app

If you use Incode's hosted or embedded onboarding app, no extra front-end integration is required after the flow is configured.

When the user reaches the ID chooser screen, the app shows a digital ID option alongside the normal document capture options, when all of the following are true:

  • Digital IDs are enabled in the flow configuration.
  • Apple Wallet or Google Wallet is enabled for the flow.
  • The user's device and browser support the wallet method.
  • The user has a compatible digital ID in their wallet.

The user taps the wallet option, confirms the wallet presentation in Apple Wallet or Google Wallet, and returns to the onboarding flow after the encrypted response is verified.

If the wallet flow cannot be completed, the user can continue with the regular ID capture fallback path depending on your flow configuration.

Option 2: Use the Web SDK ID manager

Initialize an active Flow session containing the ID Capture module with digital IDs enabled. Resolve its complete configuration before creating the manager:

import { setup } from '@incodetech/core';
import { resolveDashboardModuleConfig } from '@incodetech/core/flow';
import { createIdCaptureManager } from '@incodetech/core/id';

await setup({
  apiURL: 'https://demo-api.incodesmile.com',
  token: 'YOUR_SESSION_TOKEN',
});
const config = await resolveDashboardModuleConfig({ moduleKey: 'ID' });
const manager = createIdCaptureManager({ config });
const unsubscribe = manager.subscribe((state) => {
  if (state.status === 'chooser') {
    // Offer a digital-ID button only if availableDocumentTypes includes 'digitalId'.
    // Render the other available types as fallback choices.
  } else if (state.status === 'deviceWallet') {
    // Show progress for requesting/decrypting; wait for the device sheet in presenting.
    // In failed, render state.failReason with retry and alternative-method actions.
  } else if (state.status === 'finished') {
    // This ID step completed. Obtain verified attributes through session results.
  } else if (state.status === 'error' || state.status === 'closed') {
    // Render the error or leave the verification surface.
  }
});
manager.load();

function selectDigitalId() {
  const state = manager.getState();
  if (state.status === 'chooser' && state.availableDocumentTypes.includes('digitalId')) {
    manager.selectDocument('digitalId');
  }
}

function retryWallet() {
  const state = manager.getState();
  if (state.status === 'deviceWallet' && state.phase === 'failed') {
    manager.deviceWalletRetry();
  }
}

function useAnotherMethod() {
  const state = manager.getState();
  if (state.status === 'deviceWallet' && state.phase === 'failed') {
    manager.deviceWalletUseAnotherMethod();
  }
}

function dispose() {
  unsubscribe();
  manager.stop();
}

Connect selectDigitalId, retryWallet, and useAnotherMethod to user actions. Call dispose when unmounting. Available methods depend on configuration and browser support; selection can use another supported digital-ID route where configured. For a wallet-only integration without the ID manager, use the standalone APIs.

The ID state reference describes wallet phases and failure values. Success reaches finished. Choosing another method returns to the chooser when available, otherwise closed.

Standalone integration

Both entry points run the same handshake with no Incode UI:

Entry point Shape Use it when
renderWallet() One call with onSuccess / onError callbacks You need a single button and a final outcome. Its callbacks and error codes mirror Web SDK 1.x renderWallet, which eases a port.
createDeviceWalletManager() A manager with subscribe() / getState() over requesting → presenting → decrypting, plus retry() and useAnotherMethod() You want your own screen for each phase, or a built-in retry.

renderWallet()

import { setup } from '@incodetech/core';
import { renderWallet } from '@incodetech/core/id';

// Create the session before the tap, so the tap only has to open the wallet.
await setup({
  apiURL: 'https://demo-api.incodesmile.com',
  token: 'your-session-token',
});

const walletButton = document.querySelector<HTMLButtonElement>('#wallet-button');

walletButton?.addEventListener('click', () => {
  // Call it inside the handler: the wallet sheet needs the click's user activation.
  void renderWallet({
    onSuccess: (result) => {
      // The handshake completed. Read the final result from the session.
      console.log('Wallet decision:', result.decision);
    },
    onError: (error) => {
      // Show your own copy for error.code, then offer a retry or document capture.
      console.error(error.code, error.message);
    },
  });
});

Parameters

Parameter Type Required Description
onSuccess (result: DeviceWalletDecryptResponse) => void ✅ Called when the handshake completes. A 'REJECTED' decision arrives here too.
onError (error: RenderWalletError) => void ✅ Called when the handshake fails. See Error codes.
provider 'apple_web' | 'google' ❌ Forces a wallet. Omit it to pick one from the browser — see Provider detection.

RenderWalletParams also accepts credentials and services. These replace the browser's Digital Credentials access and Incode's network calls in unit tests. Production integrations omit them.

renderWallet() returns Promise<void>, which resolves after onSuccess or onError runs. Failures arrive through onError, not as a rejected promise. Each call runs one handshake and cleans up after itself. To retry, call renderWallet() again from a new user gesture.

Success result

type DeviceWalletDecryptResponse = {
  success: boolean;
  decision?: 'ACCEPTED' | 'REJECTED';
  [key: string]: unknown;
};

onSuccess confirms that Incode received and processed the wallet response. It is not the final verification result, and it carries no identity attributes — read those from the session (see Reading results). Don't depend on response fields other than success and decision.

Error codes

type RenderWalletError = {
  code: 'UNSUPPORTED_PLATFORM' | 'NO_CREDENTIAL' | 'WALLET_ERROR';
  message: string;
};
code Meaning Suggested UX
UNSUPPORTED_PLATFORM The browser has no Digital Credentials API. Reported immediately, before any network call. Hide the wallet option and offer document capture.
NO_CREDENTIAL The wallet sheet closed without returning a credential. The user dismissed it or has no eligible ID. Neutral copy. Offer a retry or another method.
WALLET_ERROR Any other failure: an Incode request failed or the browser rejected the wallet request — for example, because the user activation expired or the page runs in an iframe without the wallet permission. Offer a retry, then another method.

Browsers report a dismissed sheet differently, so a user who cancels can surface as NO_CREDENTIAL or WALLET_ERROR. Treat both as retryable. message is an English developer message — show your own copy, keyed on code.

createDeviceWalletManager()

import { setup } from '@incodetech/core';
import {
  createDeviceWalletManager,
  type DeviceWalletManager,
  type DeviceWalletState,
} from '@incodetech/core/id';

await setup({
  apiURL: 'https://demo-api.incodesmile.com',
  token: 'your-session-token',
});

// walletButton, retryButton, and fallbackButton are your own <button> elements.
// showSpinner, showMessage, showDone, showRetry, and startDocumentCapture are your own UI functions.
let manager: DeviceWalletManager | undefined;

function render(state: DeviceWalletState) {
  switch (state.status) {
    case 'requesting':
    case 'decrypting':
      showSpinner();
      break;
    case 'presenting':
      showMessage('Choose your ID in the wallet.');
      break;
    case 'finished':
      manager?.stop();
      showDone(state.decision);
      break;
    case 'failed':
      showRetry(state.reason);
      break;
    case 'closed':
      manager?.stop();
      startDocumentCapture();
      break;
  }
}

walletButton.addEventListener('click', () => {
  manager?.stop();
  // Creating the manager starts the handshake, so create it inside the handler.
  manager = createDeviceWalletManager();
  manager.subscribe(render);
  // subscribe() reports later changes only. Render the first state yourself:
  // UNSUPPORTED_PLATFORM is already decided when the manager is created.
  render(manager.getState());
});

// retry() reopens the wallet sheet, so it needs a new user gesture too.
retryButton.addEventListener('click', () => manager?.retry());
fallbackButton.addEventListener('click', () => manager?.useAnotherMethod());

States

status Meaning Properties
requesting Fetching the wallet request from Incode platform
presenting The OS wallet sheet is open platform
decrypting Incode is processing the wallet response platform
finished The handshake completed. Terminal. A 'REJECTED' decision lands here too. platform, decision, response
failed The handshake failed. Recover with retry() or useAnotherMethod(). platform, reason
closed You called useAnotherMethod(). Terminal. platform
  • platform is the resolved wallet backend: 'ios' for Apple Wallet and 'android' for Google Wallet. It follows provider detection, not the device OS.
  • decision is 'ACCEPTED', 'REJECTED', or undefined. response is the full DeviceWalletDecryptResponse.
  • reason uses the same three codes as RenderWalletError — see Error codes.

Methods

Method Description When to use
retry() Restarts the handshake from the beginning When failed. Call it from a user gesture.
useAnotherMethod() Leaves the wallet path and moves to closed When failed, to fall back to document capture
getState() Returns the current state Anytime
subscribe(listener) Calls listener on every later state change. Returns an unsubscribe function. Anytime
stop() Stops the handshake, including in-flight requests, and releases resources After finished or closed, or when unmounting

createDeviceWalletManager() accepts the same provider, credentials, and services options as renderWallet().

Provider detection

Omit provider and the SDK picks the wallet from the browser. It uses apple_web on iOS, iPadOS, and Safari on macOS, and google everywhere else — including Chrome on macOS. Pass provider to force one.

Retrieving results

Wallet verification writes to the onboarding session, not to your page. Read the results from your backend with the standard Incode result APIs:

  • OCR data (GET /omni/get/ocr-data) — the verified identity attributes. For wallet verifications, documentSubmissionMethod is IMPORTED_CREDENTIAL and credentialsProvider names the wallet (for example, APPLE or GOOGLE).
  • Images (GET /omni/get/images) — the mDL portrait.
  • Score (GET /omni/get/score) — validation results for decisioning.
  • Webhooks — session status transitions.

Treat onSuccess and the finished state as confirmation that the wallet response was submitted. Make the final decision from the session results.

For Incode Hosted onboarding app customers:

  1. Enable Digital IDs in the flow configuration.
  2. Let the wallet option appear in the hosted chooser screen.
  3. Use standard Incode session result and webhook handling after completion.

For Web SDK customers using the built-in ID capture UI:

  1. Enable Digital IDs in the flow configuration.
  2. Use the chooser wallet button by default.
  3. Use the ID manager when you need to own the chooser and its entry points.

For Web SDK customers with fully custom UI:

  1. Enable Digital IDs in the flow configuration.
  2. Create your own wallet button.
  3. Call selectDocument('digitalId') from the button handler when that option is available.
  4. Subscribe to the ID manager's deviceWallet, finished, error, and closed states.
  5. Fetch final results from the session using standard Incode result APIs.

Error and fallback handling

Wallet presentation can fail when:

  • The device or browser does not support Digital Credentials APIs.
  • The user does not have a compatible digital ID in their wallet.
  • The user cancels the wallet presentation.
  • Apple Wallet domain approval is not complete.
  • The wallet response cannot be decrypted or verified.

Your application should provide a fallback path, such as retrying wallet verification or continuing with physical ID capture.

Testing

Test on a physical phone that has an ID in Apple Wallet or Google Wallet. The phone must load your page over HTTPS: a LAN address such as http://192.168.1.20:5173 is not a secure context, so the SDK reports UNSUPPORTED_PLATFORM. For Apple Wallet, test on a domain approved for your integration.

  • Google Wallet offers a sandbox with test ID passes. Follow Google's sandbox and test ID pass guides, or the device setup in Incode's Google Wallet ID testing steps. Use a dedicated test device and account — switching Google Wallet environments can affect existing Wallet data.
  • Apple Wallet needs an ID provisioned in Wallet on the test iPhone. Ask your Incode Representative about Apple Wallet test options.

Troubleshooting

Symptom Likely cause Fix
UNSUPPORTED_PLATFORM on a phone that has an ID in the wallet The page loads over plain HTTP, runs in an in-app WebView, or the browser lacks the Digital Credentials API Open the page over HTTPS in a current Safari (iOS) or Chrome (Android)
WALLET_ERROR right after the tap The wallet did not start inside the tap handler, or a cross-origin iframe lacks allow="digital-credentials-get" Create the session before the tap, start the wallet in the handler, and grant the iframe permission
Apple Wallet works on one domain but not another The failing domain is not approved for your integration yet Confirm the domain with your Incode Representative
NO_CREDENTIAL although the wallet holds an ID The ID does not match what Incode requests (issuing state or document type), or Google Wallet runs in a different environment (sandbox or production) than the ID Check Supported Digital IDs and the Google Wallet environment

Direct API integration

The SDK calls Incode's wallet endpoints for you. To run a wallet verification without the SDK, contact your Incode Representative — direct API access is available on request.

Scope notes

  • The integration model is flow-based. Requested attributes and enabled wallet methods are configured in Dashboard, not passed ad hoc from the browser.