# Android SDK

# Getting started

Latest release: Version 1.30.0 (Changelog)

# Requirements

# Resources

# Installation

It's always a good idea to usе the latest version

Add the SumSub maven repository to the repositories section in your build.gradle file:

repositories {
  maven { url "https://maven.sumsub.com/repository/maven-public/" }
}

Add the following dependencies to your build.gradle file:

dependencies {
    // SumSub core
    implementation "com.sumsub.sns:idensic-mobile-sdk:$latestVersion"
    // Video Identification module
    implementation "com.sumsub.sns:idensic-mobile-sdk-videoident:$latestVersion"
}

# Basic Usage

Make sure you complete the Backend routines before initializing the SDK

# Initialization

Kotlin example


// use the 'accessToken' that you generated on your backend
val accessToken = "..."

val tokenExpirationHandler = object : TokenExpirationHandler {
    override fun onTokenExpired(): String? {
        // Access token expired
        // get a new one and pass it to the callback to re-initiate the SDK
        val newToken = "..." // get a new token from your backend
        return newToken
    }
}

val snsSdk = SNSMobileSDK.Builder(this)
    .withAccessToken(accessToken, onTokenExpiration = tokenExpirationHandler)
    .withLocale(Locale("en"))
    .build()

snsSdk.launch()

Java example

// use the 'accessToken' that you generated on your backend
String accessToken = "...";

TokenExpirationHandler tokenUpdater = () -> {
        // Access token expired
        // get a new one and pass it to the callback to re-initiate the SDK
        String newAccessToken = "..."; // get a new token from your backend
        return newAccessToken;
    };

SNSMobileSDK.SDK snsSdk = new SNSMobileSDK.Builder(requireActivity())
        .withAccessToken(accessToken, tokenUpdater)
        .withLocale(new Locale("en"))
        .build();

# Configuration

# Applicant Data

If it's required, you could provide an email and/or a phone number that will be assigned to the applicant initially.

Kotlin example

val snsSdk = SNSMobileSDK.Builder(this)
    ...
    .withConf(SNSInitConfig(email = "...", phone = "..."))
    .build()

snsSdk.launch()

Java example


SNSMobileSDK.SDK snsSdk = new SNSMobileSDK.Builder(requireActivity())
        ...
        .withConf(new SNSInitConfig("...", "...", null))
        .build();

# Preferred Documents

For IDENTITY* steps, it's possible to specify the preferred country and document type to be selected automatically, thereby bypassing the DocType Selector screen. Note that the parameters provided will be applied only if the corresponding combination of country and idDocType is allowed at the step according to the level configuration.

Kotlin example

val snsSdk = SNSMobileSDK.Builder(this)
    ...
    .withPreferredDocumentDefinitions(mapOf(
        "IDENTITY" to SNSDocumentDefinition(idDocType = "DRIVERS", country = "USA")
    ))
    .build()

Java example

SNSMobileSDK.SDK snsSdk = new SNSMobileSDK.Builder(requireActivity())
        ...
        .withPreferredDocumentDefinitions(Map.of(
            "IDENTITY", new SNSDocumentDefinition("DRIVERS", "USA")
        ))
        .build();

# Handlers

# Token Expiration Handler

Because of the limited lifespan of the accessToken, it's important to handle the situation correctly when the token expires and needs to be refreshed. In order to do this provide tokenHandler for the SDK Builder. The handler should make a call to your backend, obtain a new access token and then pass it back to the SDK by returning its value.

val tokenHandler = object : TokenExpirationHandler {
    override fun onTokenExpired(): String? {
        val newToken = ....
        return newToken
    }
}
TokenExpirationHandler tokenUpdater = () -> {
        String newAccessToken = "...";
        return newAccessToken;
    };

onTokenExpired is called on a non-UI thread

# Callbacks

# On SDK State changes

Use withHandlers(onStateChanged=onSDKStateChangedHandler) or .withStateChangedHandler(stateChangedHandler) in the SDK builder to get notified about changes in the flow of the verification process. The callback takes two parameters:

  • newState is the current SDK State
  • prevState is the previous state value

The following example lists all the possible states:

val onSDKStateChangedHandler: (SNSSDKState, SNSSDKState) -> Unit = { newState, prevState ->
    Timber.d("onSDKStateChangedHandler: $prevState -> $newState")

    when (newState) {
        is SNSSDKState.Ready -> Timber.d("SDK is ready")
        is SNSSDKState.Failed -> {
            when (newState) {
                is SNSSDKState.Failed.ApplicantNotFound -> Timber.e(newState.message)
                is SNSSDKState.Failed.ApplicantMisconfigured -> Timber.e(newState.message)
                is SNSSDKState.Failed.InitialLoadingFailed -> Timber.e(newState.exception,"Initial loading error")
                is SNSSDKState.Failed.InvalidParameters -> Timber.e(newState.message)
                is SNSSDKState.Failed.NetworkError -> Timber.e(newState.exception,newState.message)
                is SNSSDKState.Failed.Unauthorized -> Timber.e(newState.exception,"Invalid token or a token can't be refreshed by the SDK. Please, check your token expiration handler")
                is SNSSDKState.Failed.Unknown -> Timber.e(newState.exception, "Unknown error")
            }
        }
        is SNSSDKState.Initial -> Timber.d("No verification steps are passed yet")
        is SNSSDKState.Incomplete -> Timber.d("Some but not all verification steps are passed over")
        is SNSSDKState.Pending -> Timber.d("Verification is in pending state")
        is SNSSDKState.FinallyRejected -> Timber.d("Applicant has been finally rejected")
        is SNSSDKState.TemporarilyDeclined -> Timber.d("Applicant has been declined temporarily")
        is SNSSDKState.Approved -> Timber.d("Applicant has been approved")
    }
}

val snsSdkBuilder = SNSMobileSDK.Builder(this).withHandlers(onStateChanged = onSDKStateChangedHandler)
SNSStateChangedHandler stateChangedHandler = (previousState, currentState) -> {

    Timber.d("The SDK state was changed: " + previousState + " -> " + currentState);

    if (currentState instanceof SNSSDKState.Ready) {
        Timber.d("SDK is ready");
    } else if (currentState instanceof SNSSDKState.Failed.Unauthorized) {
        Timber.e(((SNSSDKState.Failed.Unauthorized) currentState).getException(), "Invalid token or a token can't be refreshed by the SDK. Please, check your token expiration handler");
    } else if (currentState instanceof SNSSDKState.Failed.Unknown) {
        Timber.e(((SNSSDKState.Failed.Unknown) currentState).getException(), "Unknown error");
    } else if (currentState instanceof SNSSDKState.Initial) {
        Timber.d("No verification steps are passed yet");
    } else if (currentState instanceof SNSSDKState.Incomplete) {
        Timber.d("Some but not all verification steps are passed over");
    } else if (currentState instanceof SNSSDKState.Pending) {
        Timber.d("Verification is in pending state");
    } else if (currentState instanceof SNSSDKState.FinallyRejected) {
        Timber.d("Applicant has been finally rejected");
    } else if (currentState instanceof SNSSDKState.TemporarilyDeclined) {
        Timber.d("Applicant has been declined temporarily");
    } else if (currentState instanceof SNSSDKState.Approved) {
        Timber.d("Applicant has been approved");
    }
};

SNSMobileSDK.SDK snsSdk = new SNSMobileSDK.Builder(requireActivity()).withStateChangedHandler(stateChangedHandler).build();

# Applicant Actions

If you are using Applicant Actions, use additional state for handling action's result

val onSDKStateChangedHandler: (SNSSDKState, SNSSDKState) -> Unit = { newState, prevState ->
    Timber.d("onSDKStateChangedHandler: $prevState -> $newState")

    // Flow action has been completed
    if (newState is SNSSDKState.ActionCompleted) {
        val actionId = newState.actionId
        val type = newState.type
        val answer = newState.answer
        val payload = newState.payload
    }
}
SNSStateChangedHandler stateChangedHandler = (previousState, currentState) -> {

    Timber.d("The SDK state was changed: " + previousState + " -> " + currentState);

    if (currentState instanceof SNSSDKState.ActionCompleted) {
        SNSSDKState.ActionCompleted actionState = (SNSSDKState.ActionCompleted) currentState;
        String actionId = actionState.getActionId();
        FlowActionType type = actionState.getType();
        String answer = actionState.getAnswer();
        Map<String, Object> payload = actionState.getPayload();
    }
};

# On SDK Errors

Use withHandlers(onError=onSDKErrorHandler) in the SDK builder to know about errors that occur within the SDK. Refer to the example below to see how:

val onSDKErrorHandler: (SNSException) -> Unit = { exception ->
    Timber.d("The SDK throws an exception. Exception: $exception")

    when (exception) {
        is SNSException.Api -> Timber.d("Api exception. ${exception.description}")
        is SNSException.Network -> Timber.d(exception, "Network exception.")
        is SNSException.Unknown -> Timber.d(exception, "Unknown exception.")
    }
}

val snsSdkBuilder = SNSMobileSDK.Builder(this).withHandlers(onError = onSDKErrorHandler)

SNSErrorHandler errorHandler = e -> {
    Timber.d("The SDK throws an exception. Exception: %s", e);

    if (e instanceof SNSException.Api) {
        Timber.d("Api exception. %s", ((SNSException.Api) e).getDescription());
    } else if (e instanceof SNSException.Network) {
        Timber.d(e, "Network exception.");
    } else if (e instanceof SNSException.Unknown) {
        Timber.d(e, "Unknown exception.");
    }

};

SNSMobileSDK.SDK snsSdk = new SNSMobileSDK.Builder(requireActivity()).withErrorHandler(errorHandler).build();

# On SDK completion

An optional callback to get notified when the SDK is closed:

val onSDKCompletedHandler: (SNSCompletionResult, SNSSDKState) -> Unit = { result, state ->
    when (result) {
        is SNSCompletionResult.SuccessTermination -> Timber.d("The SDK finished successfully")
        is SNSCompletionResult.AbnormalTermination -> Timber.e(result.exception, "The SDK got closed because of errors")
    }
}

val snsSdkBuilder = SNSMobileSDK.Builder(this).withHandlers(onCompleted = onSDKCompletedHandler)
SNSCompleteHandler completeHandler = (result, state) -> {
    Timber.d("The SDK is finished. Result: " + result + " , State: " + state);
    Toast.makeText(applicationContext, "The SDK is finished. Result: $result, State: $state", Toast.LENGTH_SHORT).show();

    if (result instanceof SNSCompletionResult.SuccessTermination) {
        Timber.d(result.toString());
    } else if (result instanceof SNSCompletionResult.AbnormalTermination) {
        Timber.d(((SNSCompletionResult.AbnormalTermination) result).getException());
    }
};

SNSMobileSDK.SDK snsSdk = new SNSMobileSDK.Builder(requireActivity()).withCompleteHandler(completeHandler).build();

The callback takes two parameters:

  • result:
    • SNSCompletionResult.SuccessTermination - A user clicks on the cancel button.
    • SNSCompletionResult.AbnormalTermination - an error occurred. Look at the exception object if you want to get more information
  • state: The state at which the SDK was closed. For possible states refer to the following Section

# On Action Result

An optional handler for getting liveness result and controlling action scenario.

The handler takes two parameters: -actionId : String - Action ID -answer : String - Liveness module answer. Possible values: "GREEN", "YELLOW", "RED", "ERROR" or null

The handler must return SNSActionResult. Currently the following values supported: -SNSActionResult.Continue - continue default action scenario (show result screen etc.) -SNSActionResult.Cancel - cancel default action scenario (close sdk without result screen)

val onActionResultHandler: SNSActionResultHandler = object : SNSActionResultHandler {
   override fun onActionResult(actionId: String, actionType: String, answer: String?, allowContinuing: Boolean): SNSActionResult {
       Timber.d("Action Result: actionId: $actionId answer: $answer")
       // use default scenario
       return SNSActionResult.Continue
   }
}

val snsSdkBuilder = SNSMobileSDK.Builder(this).withActionResultHandler(onActionResult)
SNSActionResultHandler actionResultHandler = (actionId, actionType, answer, allowContinuing) -> {
    Timber.d("Action Result: actionId: " + actionId + ", answer: " + answer);
    return SNSActionResult.Continue;
};

SNSMobileSDK.SDK snsSdk = new SNSMobileSDK.Builder(requireActivity()).withActionResultHandler(actionResultHandler).build();

# Events

Providing events callback allows you to be aware of the events happening along the processing.

Events are passed into the callback as instances of a class inherited from the base SNSEvent class, this way each event has its eventType and some parameters packed into payload dictionary. So, depending on your needs, you can get event parameters either by examining the payload directly or by casting the given event instance to a specific SNSEvent* class according to its type.

val onEventHandler: SNSEventHandler = object : SNSEventHandler {
    override fun onEvent(event: SNSEvent) {
        when (event) {
            is SNSEvent.SNSEventStepInitiated -> {
                Timber.d("onEvent: step initiated")
            }
            is SNSEvent.SNSEventStepCompleted -> {
                Timber.d("onEvent: step completed")
            }
        }
    }
}

val snsSdkBuilder = SNSMobileSDK.Builder(this).withEventHandler(onEventHandler)
SNSEventHandler eventHandler = snsEvent -> {
    if (snsEvent instanceof SNSEvent.SNSEventStepInitiated) {
        Timber.d("onEvent: step initiated");
    } else if (snsEvent instanceof SNSEvent.SNSEventStepCompleted) {
        Timber.d("onEvent: step completed");
    }
};

SNSMobileSDK.SDK snsSdk = new SNSMobileSDK.Builder(requireActivity()).withEventHandler(eventHandler).build();

# Icon handler

By providing an icon handler you are able to provide drawable for specific positions on Sumsub SDK screens. We currently only support instructions icons and document icons (for the document selector screen). You will be provided with an icon key and the themed context. The handler should return a drawable or null if the drawable is not needed.

The SDK uses the following keys:

  • default/do_idCard - an icon for the identity card instruction, the DO block
  • default/do_idCard_backSide - an icon for the back side of the identity card instruction, the DO block
  • default/do_passport - an icon for the password instruction, the DO block
  • default/dont_idCard - an icon for the identity card instruction, the DON'T block
  • default/dont_idCard_backSide - an icon for the back side of the identity card instruction, the DON'T block
  • default/dont_passport - an icon for the password instruction, the DON'T block
  • default/facescan - an icon for the Liveness instruction
  • IdentityType/<DOCUMENT TYPE> - an icon for the document's item on the Document Selector screen, where <DOCUMENT TYPE> is PASSPORT, DRIVERS, RESIDENCE_PERMIT or another document
  • Flag/<COUNTRY_CODE> - an icon for a country flag, where <COUTRY_CODE> is ISO 3166-1 alpha-2 country code

You can provide your own icons or overwrite existing ones by extending the SNSDefaultIconHandler. If no handler has been provided the SNSDefaultIconHandler is used.

val iconHandler = object : SNSIconHandler {
    override fun onResolveIcon(context: Context, key: String): Drawable? {
        val iconRes = when {
            key == "default/do_idCard" -> com.sumsub.sns.core.R.drawable.sns_ic_intro_do
            key == "default/do_passport" -> com.sumsub.sns.core.R.drawable.sns_ic_intro_do_passport
            key == "default/dont_idCard" -> com.sumsub.sns.core.R.drawable.sns_ic_intro_dont
            key == "default/dont_passport" -> com.sumsub.sns.core.R.drawable.sns_ic_intro_dont_passport
            key == "default/facescan" -> com.sumsub.sns.core.R.drawable.sns_ic_intro_liveness
            key == "default/do_idCard_backSide" -> com.sumsub.sns.core.R.drawable.sns_ic_intro_do_back
            key == "default/dont_idCard_backSide" -> com.sumsub.sns.core.R.drawable.sns_ic_intro_dont_back
            key == "IdentityType/PASSPORT" -> com.sumsub.sns.core.R.drawable.sns_ic_iddoc_passport
            key == "IdentityType/DRIVERS" -> com.sumsub.sns.core.R.drawable.sns_ic_iddoc_driving_license
            key == "IdentityType/RESIDENCE_PERMIT" -> com.sumsub.sns.core.R.drawable.sns_ic_iddoc_residence_permit
            key.startsWith("IdentityType/") -> com.sumsub.sns.core.R.drawable.sns_ic_iddoc_id_card
            else -> -1
        }

        return iconRes.takeIf { it != -1 }?.let { ResourcesCompat.getDrawable(context.resources, it, context.theme) }
    }
}

val snsSdkBuilder = SNSMobileSDK.Builder(this).withIconHandler(iconHandler)

# Video Identification

The VideoIdent is an optional module that is required only if you are going to use the Video Identification during the verification flow. In order to enable the module, please add the following dependency into your build.gradle file.

dependencies {
    // Video Identification module
    implementation "com.sumsub.sns:idensic-mobile-sdk-videoident:$latestVersion"
}

# Debugging

# Logs

If you want to see the logs within the SDK, please, use the following code:

// default is 'false'
val snsSdkBuilder = SNSMobileSDK.Builder(this).withDebug(true)
SNSMobileSDK.SDK snsSdk = new SNSMobileSDK.Builder(requireActivity()).withDebug(true);

Don't forget to disable the flag for the release build.

# Custom Logger

You can provide SumSub Android SDK with custom logger using the .withLogTree method. Create your custom logger, for example:

class CustomTree : Logger {
  override fun d(tag: String, message: String, throwable: Throwable?) {
    Log.d(tag, message, throwable)
  }

  override fun e(tag: String, message: String, throwable: Throwable?) {
    Log.e(tag, message, throwable)
  }

  override fun i(tag: String, message: String, throwable: Throwable?) {
    Log.i(tag, message, throwable)
  }

  override fun v(tag: String, message: String, throwable: Throwable?) {
    Log.v(tag, message, throwable)
  }

  override fun w(tag: String, message: String, throwable: Throwable?) {
    Log.w(tag, message, throwable)
  }

}

Right now you can use your custom log tree:

val snsSdkBuilder = SNSMobileSDK.Builder(this).withLogTree(CustomTree())

# Customization

The SDK can be customized: you can change styles, colors, typefaces, etc.

See also Theme API and Visual Guide for details.

# Localization

.withLocale(Locale("en")) gives you ability to switch between locales on initialization You can customize or localize the texts used within the SDK through the MSDK Translations tool in the dashboard. Please check the list of supported locales by SDK here.

# Strings

In addition to the MSDK Translations tool and the .withLocale(Locale("en")) described above, you can use withConf builder method that allows you to define the strings locally.

val config = SNSInitConfig(
  email = "[email protected]",
  phone = "+1 234 567890",
  strings = mapOf(
    "sns_oops_network_title" to "Oops! Seems like the network is down.",
    "sns_oops_network_html" to "Please check your internet connection and try again.",
    "sns_oops_action_retry" to "Try again",
  )
)
snsSdkBuilder.withConf(config)
Map<String, String> strings = new HashMap<>();
            strings.put("sns_oops_network_title", "Oops! Seems like the network is down.");
            strings.put("sns_oops_network_html", "Please check your internet connection and try again.");
            strings.put("sns_oops_action_retry", "Try again");

SNSInitConfig config = new SNSInitConfig("[email protected]", "+11231234567", strings);
SNSMobileSDK.SDK snsSdk = new SNSMobileSDK.Builder(requireActivity())
        ...
        .withConf(config)
        .build();

⚠️ Note that the witLocale method does not affect these strings, thus it's up to you to use the required localization.

# 'Terms and Conditions', 'Private Policy'

You can specify your 'Terms and Conditions' and 'Privacy Policy' by HTML or URL. Use the following keys in MSDK Translations sns_tos_GTC_html (Terms and Conditions) and sns_tos_PP_html (Privacy Policy) to specify HTML and sns_tos_GTC_url (Terms and Conditions) and sns_tos_PP_url (Privacy Policy) to load from URL.

# Action Flow

In order to run the SDK in applicant action mode, you need to create an applicant flow of Applicant actions type in the dashboard and specify its name as the flowName initialization parameter. Also, it'll be required to make an Access Token not only with the userId parameter, but with the externalActionId one as well.

Aside from the notes above, you manage the sdk the same way that you do with regular flows, the only difference is in how you get the action's result.

You can specify the type of file requested from the gallery by overriding the resource string.

<string name="sns_gallery_type">*/*</string>

Users can preview PDF files on Android devices running 5.0 Lollipop and higher.

# Low-light performance for our liveness detection

The SDK can operate using screen light in low-light conditions. The application must have additional permissions to change screen brightness. Accordingly, you can request that the user opens settings and adds the application to their device’s “trusted list.”


val intent = Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)

# Analytics

The SDK collects and sends usage data to Sumsub servers. We don't track any sensitive data. Only general usage statistics is sent to us. It includes screen navigation events, UI controls interaction events and so on. We interpret the usage data in order to improve Sumsub. None of the data is sold or transferred to third parties, and we don't use it for advertisement.

If you still want to disable this feature, you can use the following to do this:

val snsSdkBuilder = SNSMobileSDK.Builder(this).withAnalyticsEnabled(false)

# Obfuscation

The Sumsub Mobile SDK contains necessary proguard rules. You don't need to add it manually.

Please, make sure you have disabled R8 full mode: in the gradle.properties, add the following line

android.enableR8.fullMode=false

# Reducing SDK size

You can reduce the size of your application's release build by removing TensorFlow library binaries, which will save about 7 MB depending on the supported architectures. However, note that removing these libraries may affect some features of the Sumsub SDK:

  • Lower face detection accuracy in some cases.
  • The client-side photo quality detector will be disabled.
  • Automatic documents capture will be disabled.

To remove TensorFlow libraries from the resulting APK (or AAB), add the following code in the application's build.gradle file:

android {

    // excludes heavy TF *.so files from the apk.
    packagingOptions {
        exclude 'lib/**/libtensorflowlite_jni.so'
    }

Last Updated: 3/20/2024, 6:59:53 PM