Skip to main content

Overview

The failure callback is invoked when device data collection fails. It provides both the session ID (if available) and an error message describing what went wrong.

Signature

Kotlin

In Kotlin, the failure callback is a lambda function with the following signature:
(sessionId: String, error: String) -> Unit
sessionId
String
The session identifier, if one was generated before the failure occurred. May be empty if the failure happened before session creation.
error
String
A description of the error that caused the collection to fail. Use this for logging and debugging.

Java

In Java, the callback is implemented as a functional interface (lambda) with a Unit return type:
(String sessionId, String error) -> Unit
Since Java lambdas require a return statement for the Unit type, always return null:
(sessionId, error) -> {
    // Your code here
    return null;
}

Usage

Kotlin Example

KountSDK.collectForSession(
    context = this,
    onSuccess = { sessionId ->
        Log.d("Kount", "Success: $sessionId")
        submitOrderWithSessionId(sessionId)
    },
    onFailure = { sessionId, error ->
        Log.e("Kount", "Collection failed: $error")
        Log.e("Kount", "Session ID: $sessionId")
        
        // Handle the failure
        handleCollectionFailure(error)
    }
)

Java Example

KountSDK.INSTANCE.collectForSession(
    this,
    (sessionId) -> {
        Log.d("Kount", "Success: " + sessionId);
        submitOrderWithSessionId(sessionId);
        return null;
    },
    (sessionId, error) -> {
        Log.e("Kount", "Collection failed: " + error);
        Log.e("Kount", "Session ID: " + sessionId);
        
        // Handle the failure
        handleCollectionFailure(error);
        
        return null; // Required for Java lambdas
    }
);

When Is It Called?

The failure callback is invoked when:
  • Network connectivity issues prevent data collection
  • The SDK configuration is invalid (missing merchant ID, invalid environment)
  • Required permissions are denied
  • The collection status becomes CollectionStatus.FAILED
  • Internal errors occur during the collection process
The callback is called asynchronously on the main thread, making it safe to update UI elements.

What to Do in the Callback

1

Log the Error

Always log errors for debugging and monitoring purposes.
KountSDK.collectForSession(
    this,
    { sessionId -> handleSuccess(sessionId) },
    { sessionId, error ->
        Log.e("Kount", "Failed with error: $error")
        if (sessionId.isNotEmpty()) {
            Log.e("Kount", "Partial session ID: $sessionId")
        }
        
        // Send to error tracking service
        if (BuildConfig.DEBUG) {
            Crashlytics.log("Kount collection failed: $error")
        }
    }
)
2

Update UI

Provide user feedback and update the UI appropriately.
KountSDK.collectForSession(
    this,
    { sessionId -> handleSuccess(sessionId) },
    { sessionId, error ->
        // Hide loading indicator
        progressBar.visibility = View.GONE
        
        // Show error message
        Toast.makeText(
            this,
            "Unable to verify device. Please try again.",
            Toast.LENGTH_LONG
        ).show()
        
        // Show retry button
        retryButton.visibility = View.VISIBLE
    }
)
3

Implement Retry Logic

Optionally retry the collection process.
private var retryCount = 0
private val maxRetries = 3

private fun attemptCollection() {
    KountSDK.collectForSession(
        this,
        { sessionId -> handleSuccess(sessionId) },
        { sessionId, error ->
            Log.e("Kount", "Attempt ${retryCount + 1} failed: $error")
            
            if (retryCount < maxRetries) {
                retryCount++
                Handler(Looper.getMainLooper()).postDelayed({
                    attemptCollection()
                }, 2000) // Wait 2 seconds before retry
            } else {
                handleMaxRetriesExceeded(error)
            }
        }
    )
}
4

Decide on Fallback

Determine whether to proceed without device data or block the transaction.
KountSDK.collectForSession(
    this,
    { sessionId -> handleSuccess(sessionId) },
    { sessionId, error ->
        Log.e("Kount", "Collection failed: $error")
        
        // Option 1: Proceed without device data
        showWarningDialog(
            "Device verification failed. You may proceed, but transaction may require additional verification."
        ) {
            proceedWithoutKount()
        }
        
        // Option 2: Block transaction
        // showErrorDialog("Unable to process transaction. Please try again later.")
    }
)

Complete Examples

Basic Error Handling

Kotlin:
class CheckoutActivity : AppCompatActivity() {
    
    private lateinit var progressBar: ProgressBar
    private lateinit var retryButton: Button
    private lateinit var checkoutButton: Button
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_checkout)
        
        progressBar = findViewById(R.id.progressBar)
        retryButton = findViewById(R.id.retryButton)
        checkoutButton = findViewById(R.id.checkoutButton)
        
        retryButton.setOnClickListener {
            initiateCollection()
        }
        
        initiateCollection()
    }
    
    private fun initiateCollection() {
        progressBar.visibility = View.VISIBLE
        retryButton.visibility = View.GONE
        checkoutButton.isEnabled = false
        
        KountSDK.collectForSession(
            this,
            { sessionId ->
                handleCollectionSuccess(sessionId)
            },
            { sessionId, error ->
                handleCollectionFailure(sessionId, error)
            }
        )
    }
    
    private fun handleCollectionSuccess(sessionId: String) {
        Log.d("Kount", "Collection successful: $sessionId")
        
        progressBar.visibility = View.GONE
        checkoutButton.isEnabled = true
        
        Toast.makeText(this, "Device verified", Toast.LENGTH_SHORT).show()
    }
    
    private fun handleCollectionFailure(sessionId: String, error: String) {
        Log.e("Kount", "Collection failed: $error")
        
        if (sessionId.isNotEmpty()) {
            Log.e("Kount", "Partial session ID: $sessionId")
        }
        
        progressBar.visibility = View.GONE
        retryButton.visibility = View.VISIBLE
        
        Toast.makeText(
            this,
            "Device verification failed. Please retry.",
            Toast.LENGTH_LONG
        ).show()
    }
}
Java:
public class CheckoutActivity extends AppCompatActivity {
    
    private ProgressBar progressBar;
    private Button retryButton;
    private Button checkoutButton;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_checkout);
        
        progressBar = findViewById(R.id.progressBar);
        retryButton = findViewById(R.id.retryButton);
        checkoutButton = findViewById(R.id.checkoutButton);
        
        retryButton.setOnClickListener(v -> initiateCollection());
        
        initiateCollection();
    }
    
    private void initiateCollection() {
        progressBar.setVisibility(View.VISIBLE);
        retryButton.setVisibility(View.GONE);
        checkoutButton.setEnabled(false);
        
        KountSDK.INSTANCE.collectForSession(
            this,
            (sessionId) -> {
                handleCollectionSuccess(sessionId);
                return null;
            },
            (sessionId, error) -> {
                handleCollectionFailure(sessionId, error);
                return null;
            }
        );
    }
    
    private void handleCollectionSuccess(String sessionId) {
        Log.d("Kount", "Collection successful: " + sessionId);
        
        progressBar.setVisibility(View.GONE);
        checkoutButton.setEnabled(true);
        
        Toast.makeText(this, "Device verified", Toast.LENGTH_SHORT).show();
    }
    
    private void handleCollectionFailure(String sessionId, String error) {
        Log.e("Kount", "Collection failed: " + error);
        
        if (!sessionId.isEmpty()) {
            Log.e("Kount", "Partial session ID: " + sessionId);
        }
        
        progressBar.setVisibility(View.GONE);
        retryButton.setVisibility(View.VISIBLE);
        
        Toast.makeText(
            this,
            "Device verification failed. Please retry.",
            Toast.LENGTH_LONG
        ).show();
    }
}

Advanced: Retry with Exponential Backoff

class CheckoutActivity : AppCompatActivity() {
    
    private var retryCount = 0
    private val maxRetries = 3
    private val baseDelay = 1000L // 1 second
    
    private fun initiateCollection() {
        progressBar.visibility = View.VISIBLE
        
        KountSDK.collectForSession(
            this,
            { sessionId ->
                retryCount = 0 // Reset on success
                handleSuccess(sessionId)
            },
            { sessionId, error ->
                handleFailureWithRetry(sessionId, error)
            }
        )
    }
    
    private fun handleFailureWithRetry(sessionId: String, error: String) {
        Log.e("Kount", "Attempt ${retryCount + 1} failed: $error")
        
        if (retryCount < maxRetries) {
            retryCount++
            val delay = baseDelay * (1 shl (retryCount - 1)) // Exponential backoff
            
            showRetryMessage("Retrying in ${delay / 1000} seconds...")
            
            Handler(Looper.getMainLooper()).postDelayed({
                initiateCollection()
            }, delay)
        } else {
            handleFinalFailure(error)
        }
    }
    
    private fun handleFinalFailure(error: String) {
        Log.e("Kount", "Max retries exceeded. Final error: $error")
        
        progressBar.visibility = View.GONE
        
        AlertDialog.Builder(this)
            .setTitle("Verification Failed")
            .setMessage("Unable to verify device after multiple attempts. Would you like to continue anyway?")
            .setPositiveButton("Continue") { _, _ ->
                proceedWithoutKount()
            }
            .setNegativeButton("Cancel") { dialog, _ ->
                dialog.dismiss()
                finish()
            }
            .show()
    }
    
    private fun proceedWithoutKount() {
        checkoutButton.isEnabled = true
        Toast.makeText(
            this,
            "Proceeding without device verification",
            Toast.LENGTH_LONG
        ).show()
    }
}

With ViewModel and LiveData

class CheckoutViewModel : ViewModel() {
    
    private val _collectionState = MutableLiveData<CollectionState>()
    val collectionState: LiveData<CollectionState> = _collectionState
    
    private var retryCount = 0
    private val maxRetries = 2
    
    fun startCollection(context: Context) {
        _collectionState.value = CollectionState.InProgress
        
        KountSDK.collectForSession(
            context,
            { sessionId ->
                retryCount = 0
                _collectionState.value = CollectionState.Success(sessionId)
            },
            { sessionId, error ->
                handleFailure(context, sessionId, error)
            }
        )
    }
    
    private fun handleFailure(context: Context, sessionId: String, error: String) {
        Log.e("Kount", "Collection failed: $error (attempt ${retryCount + 1})")
        
        if (retryCount < maxRetries) {
            retryCount++
            _collectionState.value = CollectionState.Retrying(retryCount)
            
            // Retry after delay
            viewModelScope.launch {
                delay(2000)
                startCollection(context)
            }
        } else {
            _collectionState.value = CollectionState.Failed(error)
        }
    }
    
    fun resetRetries() {
        retryCount = 0
    }
}

sealed class CollectionState {
    object InProgress : CollectionState()
    data class Retrying(val attempt: Int) : CollectionState()
    data class Success(val sessionId: String) : CollectionState()
    data class Failed(val error: String) : CollectionState()
}

Common Error Scenarios

The device may not have internet connectivity or the connection may be unstable.Handling:
{ sessionId, error ->
    if (error.contains("network", ignoreCase = true)) {
        Toast.makeText(
            this,
            "Please check your internet connection",
            Toast.LENGTH_LONG
        ).show()
        showRetryOption()
    }
}
The merchant ID or environment may not be set properly.Handling:
{ sessionId, error ->
    if (error.contains("configuration", ignoreCase = true)) {
        Log.e("Kount", "SDK not configured properly: $error")
        // This is a developer error - fix in code
        if (BuildConfig.DEBUG) {
            throw IllegalStateException("Kount SDK not configured: $error")
        }
    }
}
Location permissions may have been denied by the user.Handling:
{ sessionId, error ->
    if (error.contains("permission", ignoreCase = true)) {
        AlertDialog.Builder(this)
            .setTitle("Location Permission")
            .setMessage("Location permission helps prevent fraud. Grant permission for better protection.")
            .setPositiveButton("Grant") { _, _ ->
                requestLocationPermission()
            }
            .setNegativeButton("Skip") { _, _ ->
                proceedWithoutLocation()
            }
            .show()
    }
}
Collection may time out if it takes too long.Handling:
{ sessionId, error ->
    if (error.contains("timeout", ignoreCase = true)) {
        Log.w("Kount", "Collection timed out")
        showRetryOption("Collection timed out. Try again?")
    }
}

Best Practices

Always Log Errors

Log all failures with sufficient context for debugging and monitoring.

Implement Retry Logic

Retry collection at least once, with exponential backoff for multiple attempts.

Provide User Feedback

Show clear, user-friendly messages without exposing technical details.

Have a Fallback Plan

Decide whether to proceed without device data or block the transaction.

Testing Failure Scenarios

During development, test failure handling by:
// Test with airplane mode enabled
// Test with invalid merchant ID
KountSDK.setMerchantId("invalid")

// Test with wrong environment
KountSDK.setEnvironment(999)

// Test without location permissions
// Don't request ACCESS_FINE_LOCATION permission

Build docs developers (and LLMs) love