SDK Error Codes
This reference covers com.koard:koard-android-sdk:1.0.7, published on Maven Central. The release AAR matches the artifact distributed in the public Android repository. For device recovery steps, see Troubleshooting.
Reading an SDK Error
Catch com.koardlabs.merchant.sdk.domain.exception.KoardException and inspect its error, a com.koardlabs.merchant.sdk.domain.KoardError:
| Value | Meaning | Use |
|---|---|---|
exception.error.errorType |
A KoardErrorType classification |
Choose a recovery action by matching the concrete type |
exception.error.shortMessage |
Human-readable context | Retain a redacted diagnostic message; do not parse it as a stable code or display an unfiltered API response body |
HttpError.errorCode |
HTTP response status, when this specific subtype is present | Handle HTTP 401/403, 404, 429, or server failures as appropriate |
exception.cause |
Optional cause for some error paths | Diagnostic context only; wrapped reader failures may omit the underlying cause |
Read errorCode only after matching error.errorType as KoardErrorType.KoardServiceErrorType.HttpError.
There is no general numeric code property on KoardError or KoardException. The reader codes below describe diagnostic conditions handled by the released SDK. They are not fields you can read back from every exception. Use the Koard type for control flow and keep a fallback for unmapped codes (GeneralError). Wrapped reader errors use Koard descriptions; their messages do not necessarily retain the numeric code or raw engine detail.
Thrown Errors, Result Failures, and Reader Events
| Channel | Handling |
|---|---|
Thrown KoardException |
Catch around the SDK invocation as well as collection. Readiness can fail before a transaction Flow is returned |
Failed Result<T> |
Inspect the failure or call getOrThrow() inside try/catch. This applies to APIs such as reference-based capture/refund |
Emitted KoardTransactionResponse |
Inspect nullable actionStatus, finalStatus, statusCode, and transaction; see SDK Response Codes |
The Activity-based tap refund returns a Flow, while the reference-based refund returns Result<KoardTransaction>. A returned value or OnComplete event does not by itself establish payment success. Use the complete Running Payments handler for transaction status, partial approvals, surcharge decisions, and recovery when no terminal event arrives.
A network failure or timeout after submission can leave a payment outcome unknown. Retrieve and reconcile the existing transaction before another sale, capture, refund, or confirmation. Error handling must not automatically replay a payment. Preserve coroutine cancellation, run SDK work on a worker dispatcher, and update the UI on Main.
KoardErrorType Reference
KoardErrorType is a sealed hierarchy. The following tables list Koard-facing types your app can handle directly in the published 1.0.7 AAR. Additional reader conditions are described by their diagnostic codes below; use a fallback handler for other wrapped errors. Names in each table are relative to KoardErrorType or the nested family named in the heading. Keep a fallback branch for future types and unexpected exceptions.
Top-Level Error Types
| Error Type | When It Occurs |
|---|---|
GeneralError |
Catch-all for unmapped or unexpected errors |
CertificateError |
TLS or certificate validation failure |
BLEError |
Bluetooth/peripheral communication failure |
MainThreadError |
SDK method called on the main thread (must use a worker thread) |
NfcTransactionError |
NFC transaction-level failure |
DeviceNotProvisionedError |
Device has not been provisioned for Tap to Pay |
NotReady: Blocked Before the Tap Starts
sale(), preauth(), completePartialAuth(), and refundEmv() refresh their readiness checks first and throw a KoardException whose error.errorType is a KoardErrorType.NotReady.* value if the SDK cannot transact. The Flow is never returned, so wrap both the call and collection in try / catch. KoardSdkReadiness.notReadyReason() returns the same typed reason without attempting a transaction.
| Error Type | Meaning | What to do |
|---|---|---|
KernelAppNotInstalled |
The Visa Tap to Pay Ready app isn't installed | Route the user to install it (installKernelApp(activity)) |
DeveloperModeEnabled |
Developer options are on | Prompt the operator to disable developer mode |
NotAuthenticated |
No merchant session | Call login(...) |
NotEnrolled |
The device has not been enrolled | Set an active location, then call enrollDevice() |
NoActiveLocation |
No active location selected | Call setActiveLocation(locationId) |
ReaderNotStarted |
Enrolled with a location, but the reader connection was never started or was killed | Call prepare() to bring the session back up before retrying the tap |
Preparing |
Enrolling, generating certificates, the payment processor is coming up, or a location switch is in flight | Transient: wait for preparation and check readiness again before offering a tap |
CertificateFailed(error) |
Certificate generation failed | Inspect the associated error string; re-run refreshDeviceCertificates() |
EnrollmentFailed(error) |
Enrollment failed | Inspect the associated error string; see Troubleshooting |
PaymentProcessorFailed(error) |
The reader service failed to start | Inspect error; resetKernelService() to release the IPC connection, then retry (optionally prepare() first to re-warm) |
KoardServiceErrorType: API / HTTP Errors
Inspect KoardErrorType.KoardServiceErrorType on API failures. Methods returning Result can carry the exception as a failure; use getOrThrow() inside your try block to handle it in the same way as thrown exceptions.
HTTP responses are commonly wrapped as HttpError(errorCode), including HTTP 401 and 404. Handle those codes explicitly; do not assume they always become the separate Unauthorized or NotFound objects.
| Error Type | Description |
|---|---|
HttpError(errorCode: Int) |
Server returned an HTTP error. Inspect errorCode for the status (400, 401, 404, 500, etc.) |
InvalidRequest |
Request validation failed before sending (e.g., negative amount, missing transaction ID) |
NotFound |
Resource not found (404) |
Unauthorized |
Missing or invalid API key / session (401) |
UnexpectedError |
Unexpected server error or empty response body |
UnparseableResponse |
The response body could not be deserialized |
ConnectionError |
Network unreachable, DNS failure, or timeout |
DeviceIntegrityError: Security Checks
Thrown when the device fails security validation during enrollment or transaction preparation:
| Error Type | Reader Code | Description |
|---|---|---|
EmulatorDetected |
1000 | Running on an emulator. Use a physical device |
RootDetected |
1001 | Device is rooted or has superuser binaries |
TamperDetected |
1002 | Device tamper detection triggered |
DeveloperModeEnabled |
2000 | Developer options must be disabled |
DebugModeEnabled |
2001 | USB debugging must be disabled |
HookDetected |
2003 | Runtime instrumentation detected (Frida, Xposed, etc.) |
GenericIntegrityFailure |
-1 | Generic fallback when the underlying detail does not identify eligibility or initialization/enrollment |
TransactionErrorType: Card & Payment Errors
These public KoardErrorType.TransactionErrorType values classify transaction and validation failures. They have no public numeric-code property. They are distinct from KoardTransactionResponse.statusCode, reader display-message IDs, and HTTP status codes.
| Error Type | Description |
|---|---|
TransactionAmountNonPositive |
Amount must be greater than zero |
RefundMissingParentTransactionId |
Refund requires a parent transaction ID |
CancelOrEnter |
Cardholder prompted to cancel or confirm |
CardError |
Unrecoverable card data error |
NotAuthorisedOrDeclined |
Issuer declined the transaction |
PinRequired |
PIN entry is required |
IncorrectPin |
Cardholder entered an incorrect PIN |
ProcessingError |
Generic processing failure |
TryAnotherCard |
Card cannot complete. Try a different card |
InsertOrSwipe |
Contactless not supported. Use chip or mag-stripe |
TryAnotherChoice |
Try a different payment method |
Cancelled |
Transaction was cancelled |
StrongCvm |
Strong Customer Verification required (SCA) |
PinBypassed |
PIN entry was bypassed |
PinNotProvided |
PIN was requested but not provided |
TransactionNotAllowed |
Transaction type not allowed on this card/terminal |
NotApplicable |
Status not applicable to this transaction type |
UnknownStatus |
Unmapped status from the reader service |
TransactionError |
Generic transaction error |
EnableReader |
NFC reader needs to be enabled |
NetworkError |
Network error during transaction processing |
AuthenticationFailed |
Authentication with the payment backend failed |
CouldNotAttestError |
Device attestation failed during transaction |
TransactionInProgressError |
Another transaction is already in progress |
DeviceDisabledError |
Device has been disabled for transactions |
ErrorLoadingConfig |
Could not load transaction configuration |
TransactionApprovedUploadFailed |
Approval was reported but upload failed. Reconcile the existing transaction; do not charge again |
Reader Error Codes
These tables describe reader diagnostics using numeric codes when available on KoardTransactionResponse.statusCode. A wrapped exception does not necessarily retain a numeric code; handle it through KoardException.error and your fallback path. The category headings below are descriptions, not additional SDK class names.
Reader Communication
| Code | Meaning and recovery |
|---|---|
92 |
Failed to bind to the reader service |
93 |
Sending a message to the reader service failed |
94 |
Reader response could not be parsed |
95 |
Connector-side serialization failed |
96 |
Companion app missing. Use the SDK installation flow |
97 |
Google Play Protect must be enabled |
98 |
Another merchant app on the device currently holds the lock on the reader service. Close the other app or wait for it to finish. Calling resetKernelService() will not help because the lock is owned by a different process |
Reader Environment
| Code | Meaning and recovery |
|---|---|
10 |
Network unavailable or request timed out |
21 |
Device OS below Android 13 |
62 |
Visa Tap to Pay Ready app is outdated |
Device Eligibility
| Code | Meaning and recovery |
|---|---|
80 |
OS build is unsupported |
81 |
No hardware-backed keystore |
82 |
Elliptic-curve crypto unavailable |
83 |
AES crypto unavailable |
84 |
DES crypto unavailable |
85 |
NFC hardware missing or disabled |
86 |
Google Play Services absent or outdated |
-1 |
Underlying detail identifies eligibility failure |
Enrollment and Initialization
| Code | Meaning and recovery |
|---|---|
1 |
Enrollment already exists. Verify the intended merchant/location and readiness before transacting |
3 |
Missing device-auth public key. Re-enroll |
4 |
Enrolled device identifier not provided |
52 |
Random nonce required by enrollment missing |
-1 |
Underlying detail identifies initialization or enrollment failure |
Reader Preparation
| Code | Meaning and recovery |
|---|---|
7 |
Reader authentication failed |
11 |
Reader initialization not completed before use |
12 |
enrollDevice() not completed. Re-enroll |
17 |
Could not load config blobs |
18 |
Reader authentication service error |
20 |
Hardware keystore missing during key prep |
24 |
Device attestation failed |
25 |
Login exchange with payment service failed |
26 |
Login response missing assertion |
27 |
Login response missing crypto payload |
28 |
Entire login response was null |
29 |
Login HTTP body empty |
30 |
Login response missing auth status |
31 |
Shared secret not derived. Re-enroll |
32 |
Session keys missing. Re-enroll |
33 |
MAC/signature mismatch. Possible tampering |
34 |
MAC tag verification failed |
36 |
Payment service rejected authorization |
37 |
Auth status element empty |
49 |
Could not fetch key-rotation seed list |
50 |
TLS pinning failed. Possible MITM |
51 |
Could not derive transaction keys. Re-enroll |
87 |
Reader requested key rotation; this is not a payment approval |
88 |
Key rotation not needed; still check reader readiness |
89 |
Key rotation completed; still check reader readiness |
90 |
Key rotation failed. Re-enroll if transactions fail |
91 |
Reader did not return rotation status |
Generic and Unmapped Codes
A code of -1 indicates a generic eligibility, initialization/enrollment, or device-integrity failure. The SDK uses the detail available when wrapping the failure to classify it. Retain the wrapped error and a redacted description; do not assume every -1 has the same cause.
An unrecognized or unavailable underlying code maps to GeneralError. An unrecognized numeric code may appear in shortMessage, but it is not a typed numeric property. Keep a generic recovery path and retain redacted context for support.
Handling Errors in Code
This example enrolls the device without initiating a payment. Call it from your UI scope and supply the showMessage callback. It catches both the SDK invocation and its exceptions, preserves cancellation, and dispatches UI work to Main.
import com.koardlabs.merchant.sdk.KoardMerchantSdk
import com.koardlabs.merchant.sdk.domain.KoardErrorType
import com.koardlabs.merchant.sdk.domain.exception.KoardException
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
suspend fun enrollReader(
sdk: KoardMerchantSdk,
showMessage: (String) -> Unit
) {
try {
// Authenticate and select an active location before calling this helper.
val enrollmentMessage = withContext(Dispatchers.IO) {
sdk.enrollDevice()
}
withContext(Dispatchers.Main) { showMessage(enrollmentMessage) }
} catch (canceled: CancellationException) {
throw canceled
} catch (error: KoardException) {
val message = when (val type = error.error.errorType) {
is KoardErrorType.KoardServiceErrorType.HttpError -> when (type.errorCode) {
401, 403 -> "Sign in and verify merchant access."
429 -> "Too many requests. Wait before trying again."
else -> "The request failed (HTTP ${type.errorCode})."
}
KoardErrorType.KoardServiceErrorType.ConnectionError ->
"Check connectivity and enrollment state before trying again."
KoardErrorType.NotReady.NoActiveLocation -> "Select a location first."
KoardErrorType.NotReady.NotAuthenticated,
KoardErrorType.KoardServiceErrorType.Unauthorized -> "Sign in first."
KoardErrorType.NotReady.KernelAppNotInstalled ->
"Install the Visa Tap to Pay Ready app."
KoardErrorType.DeviceIntegrityError.DeveloperModeEnabled,
KoardErrorType.NotReady.DeveloperModeEnabled -> "Disable developer options."
else -> "Enrollment could not complete. Check setup or contact support."
}
// Record the type and a redacted shortMessage in your diagnostic handler.
withContext(Dispatchers.Main) { showMessage(message) }
}
}
For Result-returning APIs, put getOrThrow() inside the worker block and the surrounding try, then inspect the returned transaction's operation-specific status. For payments, use the canonical Running Payments implementation rather than adapting this enrollment helper into a payment retry loop. Unexpected exceptions should reach your app's fallback handler.
Troubleshooting and Support
Use Troubleshooting for NFC settings, enrollment, device integrity, and reader service contention. Include the SDK and Android versions, device model, operation, environment, full error type, redacted shortMessage, and transaction/event ID when available. For a reader event, also include statusCode, statusCodeDescription, actionStatus, and finalStatus when present. Omit API keys, credentials, and card data.
Published Sources
- Maven Central 1.0.7 AAR:
KoardException,KoardError, Koard-facing error types, and the released numeric-code mapping. - Public release artifact and changelog: readiness errors, reader service contention, API failures, and the published 1.0.7 behavior.

