Holder Functions
This guide covers the complete license lifecycle using the Kiwa eLicense SDK. You'll learn how to:
- Obtain a wallet certificate for secure API communication
- Assign a license to a device
- Issue (retrieve) licenses for a license holder
- Confirm successful license retrieval
- Decode and work with license data
Prerequisites
Dependencies
Ensure your project includes the Kiwa eLicense SDK:
implementation("com.sphereon.kiwa:kiwa-holder-sdk-all:0.13.0")
See Installation for detailed setup instructions.
SDK Initialization
Initialize the SDK scopes and obtain service instances:
val kiwaServices = sessionInstance.getKiwaServices()
val authService = kiwaServices.auth
val holderService = kiwaServices.holder
See Scopes and DI for initialization details.
Configuration
Set your Kiwa subscription key via environment variable or configuration:
export KIWA_SUBSCRIPTION_KEY=your_subscription_key_here
Or programmatically through the configuration system. See Getting Started for all configuration options.
License Lifecycle
The typical license workflow follows these steps:
Step 1: Obtaining a Wallet Certificate
Before performing license operations, authenticate and obtain an mTLS certificate:
- Android/Kotlin
- iOS/Swift
import com.sphereon.kiwa.elicense.sdk.auth.model.GetWalletCertificateRequestOptions
import com.sphereon.kiwa.elicense.sdk.intern.env.model.ApiEnvironment
// Request a wallet certificate
val options = GetWalletCertificateRequestOptions(
alias = "kiwa-wallet-certificate", // Certificate alias for storage
environment = ApiEnvironment.PROD // Target environment (DEV, TEST, ACC, PROD)
)
val result = authService.commands.getWalletCertificate.execute(options)
result.onSuccess { certResult ->
println("Certificate obtained successfully")
// Certificate is now stored and will be used for subsequent API calls
}.onFailure { error ->
println("Error obtaining certificate: ${error.message}")
}
import KiwaSdk
do {
let options = GetWalletCertificateRequestOptions(
alias: "kiwa-wallet-certificate",
keyPair: nil,
certificateSigningRequestPem: nil,
environment: .prod
)
let result = try await kiwaServices.auth.commands.getWalletCertificate.execute(args: options)
result.onSuccess { certResult in
print("Certificate obtained successfully")
}.onFailure { error in
print("Error obtaining certificate: \(error!.message)")
}
} catch {
print("Failed to get certificate: \(error)")
}
GetWalletCertificateRequestOptions
| Parameter | Type | Description |
|---|---|---|
alias | String | Unique identifier for storing the certificate |
keyPair | ManagedKeyPair? | Optional existing key pair (auto-generated if not provided) |
certificateSigningRequestPem | String? | Optional CSR in PEM format |
environment | ApiEnvironment? | Target environment (defaults to configured environment) |
The wallet certificate is typically obtained once per device and reused. The SDK automatically manages certificate storage and renewal.
Step 2: Assigning a License
After a license has been issued to a user's email address via the Kiwa IA API, assign it to the device:
- Android/Kotlin
- iOS/Swift
import com.sphereon.kiwa.elicense.sdk.holder.license.model.AssignDeviceLicenseRequest
// Create the assignment request
val assignRequest = AssignDeviceLicenseRequest(
code = "ACTIVATION_CODE_FROM_EMAIL", // Activation code received via email
email = "user@example.com", // Email address associated with the license
environment = ApiEnvironment.PROD // Optional: override default environment
)
// Execute the assignment
val assignResult = holderService.commands.assign.execute(assignRequest)
assignResult.onSuccess { result ->
println("License assigned successfully")
}.onFailure { error ->
println("Error assigning license: ${error.message}")
}
import KiwaSdk
do {
let assignRequest = AssignDeviceLicenseRequest(
environment: .prod,
code: "ACTIVATION_CODE_FROM_EMAIL",
email: "user@example.com"
)
let assignResult = try await kiwaServices.holder.commands.assign.execute(args: assignRequest)
assignResult.onSuccess { result in
print("License assigned successfully")
}.onFailure { error in
print("Error assigning license: \(error!.message)")
}
} catch {
print("Assignment failed: \(error)")
}
AssignDeviceLicenseRequest
| Parameter | Type | Description |
|---|---|---|
code | String | Activation code provided by Kiwa (via email) |
email | String | Email address of the license holder |
environment | ApiEnvironment? | Optional environment override |
Step 3: Issuing a License
Retrieve all licenses available for the authenticated holder:
- Android/Kotlin
- iOS/Swift
import com.sphereon.kiwa.elicense.sdk.holder.license.model.IssueLicenseRequest
// Create the issue request (environment is optional)
val issueRequest = IssueLicenseRequest(
environment = ApiEnvironment.PROD
)
// Issue the license
val issueResult = holderService.commands.issue.execute(issueRequest)
issueResult.onSuccess { result ->
// Access the parsed license documents
val documents = result.documents
// Access raw CBOR bytes if needed
val rawCbor: ByteArray = result.raw
// Process each license document
documents.mobileeIDdocuments.forEach { doc ->
println("License type: ${doc.docType}")
// Convert to display format
val display = doc.toSimpleDisplay()
println("Display JSON: ${display.toJsonString()}")
}
}.onFailure { error ->
println("Error issuing license: ${error.message}")
}
import KiwaSdk
do {
let issueRequest = IssueLicenseRequest(environment: .prod)
let issueResult = try await kiwaServices.holder.commands.issue.execute(args: issueRequest)
issueResult.onSuccess { result in
// Access the parsed license documents
let documents = result!.documents
// Process each license document
for doc in documents.mobileeIDdocuments {
print("License type: \(doc.docType)")
// Convert to display format
let display = doc.toSimpleDisplay()
print("Display JSON: \(display.toJsonString())")
}
}.onFailure { error in
print("Error issuing license: \(error!.message)")
}
} catch {
print("Issue failed: \(error)")
}
IssueLicenseSuccessResult
| Property | Type | Description |
|---|---|---|
documents | ElicenseIssueDocuments | Parsed license documents |
raw | ByteArray | Raw CBOR-encoded license data |
environment | ApiEnvironment | Environment where the license was issued |
The SDK automatically validates license signatures, certificates, and dates during issuance. Invalid licenses are rejected with appropriate error messages.
Step 4: Confirming License Issuance
After successfully retrieving licenses, confirm the receipt to the backend:
- Android/Kotlin
- iOS/Swift
import com.sphereon.kiwa.elicense.sdk.holder.license.model.ConfirmDeviceLicenseRequest
// Confirm the license issuance
val confirmRequest = ConfirmDeviceLicenseRequest(
environment = ApiEnvironment.PROD
)
val confirmResult = holderService.commands.confirm.execute(confirmRequest)
confirmResult.onSuccess { result ->
println("License confirmation successful")
}.onFailure { error ->
println("Error confirming license: ${error.message}")
}
import KiwaSdk
do {
let confirmRequest = ConfirmDeviceLicenseRequest(environment: .prod)
let confirmResult = try await kiwaServices.holder.commands.confirm.execute(args: confirmRequest)
confirmResult.onSuccess { result in
print("License confirmation successful")
}.onFailure { error in
print("Error confirming license: \(error!.message)")
}
} catch {
print("Confirmation failed: \(error)")
}
Decoding License Data
If you have raw CBOR-encoded license data, decode it using the decode command:
- Android/Kotlin
- iOS/Swift
// Decode raw CBOR bytes
val decodeResult = holderService.commands.decode.execute(rawCborBytes)
decodeResult.onSuccess { documents ->
documents.mobileeIDdocuments.forEach { doc ->
val display = doc.toSimpleDisplay()
println(display.toJsonString())
}
}.onFailure { error ->
println("Decode error: ${error.message}")
}
let decodeResult = try await kiwaServices.holder.commands.decode.execute(args: rawCborBytes)
decodeResult.onSuccess { documents in
for doc in documents!.mobileeIDdocuments {
let display = doc.toSimpleDisplay()
print(display.toJsonString())
}
}.onFailure { error in
print("Decode error: \(error!.message)")
}
Complete Example
Here's a complete example demonstrating the full license lifecycle:
- Android/Kotlin
- iOS/Swift
import com.sphereon.kiwa.elicense.sdk.*
import com.sphereon.kiwa.elicense.sdk.auth.model.GetWalletCertificateRequestOptions
import com.sphereon.kiwa.elicense.sdk.holder.license.model.*
import com.sphereon.kiwa.elicense.sdk.intern.env.model.ApiEnvironment
suspend fun performLicenseWorkflow(
kiwaServices: KiwaServices,
activationCode: String,
email: String,
environment: ApiEnvironment = ApiEnvironment.PROD
) {
// Step 1: Get wallet certificate
val certOptions = GetWalletCertificateRequestOptions(
alias = "kiwa-wallet-certificate",
environment = environment
)
kiwaServices.auth.commands.getWalletCertificate.execute(certOptions)
.onFailure { error ->
println("Certificate error: ${error.message}")
return
}
println("Certificate obtained")
// Step 2: Assign license
val assignRequest = AssignDeviceLicenseRequest(
code = activationCode,
email = email,
environment = environment
)
kiwaServices.holder.commands.assign.execute(assignRequest)
.onFailure { error ->
println("Assignment error: ${error.message}")
return
}
println("License assigned")
// Step 3: Issue license
val issueRequest = IssueLicenseRequest(environment = environment)
val issueResult = kiwaServices.holder.commands.issue.execute(issueRequest)
issueResult.onSuccess { result ->
println("License issued successfully")
// Display license information
result.documents.mobileeIDdocuments.forEach { doc ->
val display = doc.toSimpleDisplay()
println("Document Type: ${display.docType}")
println("Valid From: ${display.validityInfo.validFrom}")
println("Valid Until: ${display.validityInfo.validUntil}")
println("Data: ${display.toJsonString()}")
}
// Step 4: Confirm license
val confirmRequest = ConfirmDeviceLicenseRequest(environment = environment)
kiwaServices.holder.commands.confirm.execute(confirmRequest)
.onSuccess {
println("License confirmed")
}.onFailure { error ->
println("Confirmation error: ${error.message}")
}
}.onFailure { error ->
println("Issue error: ${error.message}")
}
}
import KiwaSdk
func performLicenseWorkflow(
kiwaServices: KiwaServices,
activationCode: String,
email: String,
environment: ApiEnvironment = .prod
) async {
do {
// Step 1: Get wallet certificate
let certOptions = GetWalletCertificateRequestOptions(
alias: "kiwa-wallet-certificate",
keyPair: nil,
certificateSigningRequestPem: nil,
environment: environment
)
let certResult = try await kiwaServices.auth.commands.getWalletCertificate.execute(args: certOptions)
if certResult.isFailure {
print("Certificate error")
return
}
print("Certificate obtained")
// Step 2: Assign license
let assignRequest = AssignDeviceLicenseRequest(
environment: environment,
code: activationCode,
email: email
)
let assignResult = try await kiwaServices.holder.commands.assign.execute(args: assignRequest)
if assignResult.isFailure {
print("Assignment error")
return
}
print("License assigned")
// Step 3: Issue license
let issueRequest = IssueLicenseRequest(environment: environment)
let issueResult = try await kiwaServices.holder.commands.issue.execute(args: issueRequest)
issueResult.onSuccess { result in
print("License issued successfully")
// Display license information
for doc in result!.documents.mobileeIDdocuments {
let display = doc.toSimpleDisplay()
print("Document Type: \(display.docType)")
print("Valid From: \(display.validityInfo.validFrom)")
print("Valid Until: \(display.validityInfo.validUntil)")
print("Data: \(display.toJsonString())")
}
}.onFailure { error in
print("Issue error: \(error!.message)")
}
// Step 4: Confirm license
let confirmRequest = ConfirmDeviceLicenseRequest(environment: environment)
let confirmResult = try await kiwaServices.holder.commands.confirm.execute(args: confirmRequest)
confirmResult.onSuccess { _ in
print("License confirmed")
}.onFailure { error in
print("Confirmation error: \(error!.message)")
}
} catch {
print("Workflow error: \(error)")
}
}
Error Handling
All SDK operations return a result type that can be either success or failure. Handle errors appropriately:
result.onSuccess { data ->
// Process successful result
}.onFailure { error ->
when {
error.message.contains("network") -> {
// Handle network errors
}
error.message.contains("certificate") -> {
// Handle certificate errors
}
else -> {
// Handle other errors
println("Error: ${error.message}")
}
}
}
Next Steps
- eLicense Mdocs - Learn about Mdoc format, display options, and verification
- IDK Engagement - Present licenses to verifiers via NFC/BLE