Wallet SDK · Developers

Wallet SDK

A Kotlin Multiplatform library for building mobile identity wallets - provisioning, storage, presentation, and document capture through one stable API.

The Wallet SDK gives you a complete, standards-compliant credential pipeline for Android and iOS. You integrate against clean engine interfaces; the SDK handles OpenID4VCI issuance, hardware-backed storage, ISO 18013-5 / OpenID4VP presentation, and on-device document and biometric capture underneath.

build.gradle.kts
implementation("com.credenceid:wallet-sdk:1.0.0-SNAPSHOT")

At a glance

Platforms
Android (API 26+) · iOS 14+ (XCFramework)
Language
Kotlin Multiplatform 2.2.0 · JVM 11
iOS interop
Coroutine StateFlow bridged to Swift AsyncSequence via SKIE - no manual bridging
Architecture
Clean / Hexagonal, strict inward dependency flow
Distribution
Android via CredenceID Maven (Nexus); iOS as a pre-built static WalletSDK.xcframework

Four engines, one singleton

You interact with the SDK through a single process-scoped WalletSdk instance. Engines are lazily initialized on first access - you never instantiate implementation classes directly.

WalletSdk (singleton)
   ├─ ProvisioningEngine     → OpenID4VCI credential issuance
   ├─ StorageEngine          → hardware-backed vault (StateFlow, delete)
   ├─ PresentationEngine     → ISO 18013-5 QR/BLE, OpenID4VP, DC API
   └─ DocumentCaptureEngine  → OCR, MRZ, NFC MRTD, liveness
Kotlin
val sdk = WalletSdk.getInstance(
    issuerIdentifier = IssuerIdentifier.ClientId("your_client_id"),
    issuerServiceUrl = "https://your-issuer.credenceid.com/issuer-service",
    context          = applicationContext,   // Android only - null on iOS
    storageMode      = StorageMode.PERSISTENT,
    reviewCenterUrl  = "https://your-reviewcenter.credenceid.com/reviewcenter",
)

getInstance() is thread-safe via double-checked locking. Same issuerIdentifier + issuerServiceUrl returns the cached instance with no network call; change either and the SDK discards the old instance and re-runs POST /api/public/wallet/init automatically.

Provisioning - OpenID4VCI, two phases

Issuance follows the OpenID4VCI pre-authorized code flow, split into two clear phases:

Phase 1 - Submit

submitProvisioningRequest() sends the applicant's identity to the issuer and persists a PENDING credential locally.

Phase 2 - Fetch

The issuer pushes an openid-credential-offer:// URI out-of-band (deep link or QR). fetchReadyCredential() exchanges it for the signed mDoc and flips the credential to VALID.

Kotlin
sdk.provisioningEngine.submitProvisioningRequest(params)
    .onSuccess { /* PENDING credential now observable in the vault */ }
    .onFailure { error -> showError(error.message) }

// later, when the offer URI arrives via deep link or QR scan:
sdk.provisioningEngine.fetchReadyCredential(credentialOfferUri)
    .onSuccess { credential -> /* now VALID */ }
    .onFailure { error -> /* handle TIMEOUT, NETWORK_ERROR, INVALID_OFFER */ }

Deep-link and QR delivery converge on the same call - decode the QR with any standard scanner (ZXing / ML Kit) and pass the raw string; the pre-authorized code is extracted from the offer payload for you.

Storage - reactive, hardware-backed

The StorageEngine exposes the vault as a StateFlow<List<DigitalCredential>>. Collect it once; it emits the current list immediately and again on every change. No polling, no manual refresh.

Kotlin
sdk.storageEngine.storedCredentials.collectLatest { credentials ->
    _credentialList.value = credentials
}

Credentials persist to on-device SQLite (PERSISTENT) or stay in memory for tests (EPHEMERAL). Keys are held in the platform SecureArea - Android KeyStore or iOS Secure Enclave. deleteCredential() erases the mDoc payload and destroys the hardware key irreversibly, so gate it behind explicit user confirmation.

Presentation - three modes, one state machine

All three modes run through PresentationEngine and share a single StateFlow<PresentationState>.

ModeTriggerProtocol
In-personHolder taps "Show credential"ISO 18013-5 (QR + BLE)
OnlineDeep link from a verifier siteOpenID4VP / ISO 18013-7
Browser (DC API)Web page calls navigator.credentials.get()OpenID4VP, mdoc Annex C
  1. Idle
  2. EngagementReady
  3. Connecting
  4. RequestReceived
  5. ConsentGiven
  6. Success | Error

Your UI observes the flow and reacts at each transition - render the engagement QR at EngagementReady, show the consent screen with verifierIdentity and requestedClaims at RequestReceived, and handle recoverable vs. unrecoverable failures via error.isRecoverable. The Digital Credentials API browser flow requires Android API 29+, Chrome 128+, and current Google Play Services, and only registers VALID credentials with Credential Manager.

Wallet Toolkit - capture & biometrics

Document capture, NFC, and liveness ship with the core SDK (liveness is an optional module).

Document capture
OCR and barcode extraction for national ID cards, passports, and driving licences, driven by an explicit capture-state lifecycle.
MRTD / NFC
Read the chip embedded in ePassports and travel documents.
Liveness
Passive on-device face capture via the optional wallet-sdk-liveness module (Innovatrics provider).
build.gradle.kts
// optional liveness module
implementation("com.credenceid:wallet-sdk-liveness:1.0.0-SNAPSHOT")

Security - RASP built in

Runtime Application Self-Protection is configured through RaspConfig and observable as a violation stream. Detects root/jailbreak, emulators, hooking frameworks, and attached debuggers. Use enforceBlock = false in CI and development so emulator and debugger checks are observe-only rather than blocking.

Kotlin
val config = RaspConfig.Builder()
    .enableRootDetection()
    .enableEmulatorDetection(enforceBlock = false)
    .enableHookingDetection()
    .enableDebuggerDetection(enforceBlock = false)
    .build()

Logging supports pluggable audit sinks and a Redacted<T> wrapper to keep PII out of log output. technicalMessage on error types is for internal logs only - map code enums to your own user-facing strings.

Get started

  1. Add the CredenceID Maven repo and the wallet-sdk dependency (root coordinate only - never @aar or -android-release, both strip transitive dependencies).
  2. Request an API key from your CredenceID account representative.
  3. Initialize WalletSdk in Application.onCreate().
  4. Run the sample app: ./gradlew :walletsdksample:installDebug
Let's issue digital IDs to your users.
Tell us your requirements & we'll setup a demo for you.