> For the complete documentation index, see [llms.txt](https://doc.youverify.co/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://doc.youverify.co/know-your-customer-services-kyc/sdk/android-sdk/android-liveness-sdk-new.md).

# Android Liveness SDK \*New

Our Android SDK enables seamless integration of real-time liveness detection into your mobile applications. To get started with our SDK, follow the guide below:

## Installation

To install, add the following line to your app's `build.gradle` file:

```kts
implementation("co.youverify:liveness-sdk-android:<version>")
```

Add this configuration to your app module's `build.gradle.kts` file:

```kts
defaultConfig {
    ndk {
        abiFilters.addAll(listOf("armeabi-v7a", "arm64-v8a", "x86"))
    }
}
```

## Usage

Create a Composable. We will call ours `LivenessComponent`.

Create the liveness SDK controller inside the composable, like so:

```kts
val livenessController = remember { YVLivenessSDKController(livenessConfig) }
```

> For a list of the valid config options, check [this](#options) out.

Pass the controller to the `YVLivenessSDK` UI Composable like so:

```kts
YVLivenessSDK(livenessController)
```

You can start the liveness process with the following function:

```kts
livenessController.start()
```

The start function also accepts a list of [task options](#tasks) as parameter `tasks`, example:

```kts
livenessController.start(tasks = listOf(
    MotionTaskOptions(options)
))
```

> The supplied tasks override the one provided during initialization through `YVLivenessConfig`.

Full example:

```kts
@Composable
fun LivenessComponent() {
    val livenessConfig = YVLivenessConfig(
        user = SDKUser(
            firstName = "John",
            lastName = "Doe"
        ),
    )

    val livenessController = remember { YVLivenessSDKController(livenessConfig) }

    fun startMotionsTask() {
        livenessController.start(tasks = listOf(
            MotionTaskOptions()
        ))
    }

    YourAppTheme {
        YVLivenessSDK(livenessController)
    }
}
```

Now, you can call your composable from your Activity.

## Options

| Option                   | Type     | Required | Description                                                                                                                        | Default Value | Possible Values            |
| ------------------------ | -------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------- | ------------- | -------------------------- |
| `publicKey`              | String   | No       | Your Youverify Public Merchant Key                                                                                                 | null          | Valid Youverify Public Key |
| `sandboxEnvironment`     | Boolean  | No       | Sets whether session should run in sandbox or live mode                                                                            | `true`        | `true`, `false`            |
| `tasks`                  | List     | No       | Sets tasks that need to be performed for liveness to be confirmed                                                                  | null          | See [tasks](#tasks)        |
| `user`                   | SDKUser  | No       | Sets details of user for which liveness check is being performed                                                                   | null          | See nested options below   |
| `user.firstName`         | String   | Yes      | First name of user                                                                                                                 | -             | Any string                 |
| `user.lastName`          | String   | No       | Last name of user                                                                                                                  | null          | Any string                 |
| `user.email`             | String   | No       | Email of user                                                                                                                      | null          | Any string                 |
| `branding`               | Branding | No       | Customizes UI to fit your brand                                                                                                    | null          | See nested options below   |
| `branding.color`         | String   | No       | Sets your branding color                                                                                                           | null          | Valid hex or RGB string    |
| `branding.logo`          | String   | No       | Sets your logo                                                                                                                     | null          | Valid image link           |
| `branding.name`          | String   | No       | Sets your branding name                                                                                                            | null          | Any string                 |
| `branding.hideLogo`      | Boolean  | No       | Hides logo                                                                                                                         | `false`       | `true`, `false`            |
| `branding.showPoweredBy` | Boolean  | No       | Shows powered by logo                                                                                                              | `false`       | `true`, `false`            |
| `branding.poweredByText` | String   | No       | Customizes the "Powered By" text                                                                                                   | "Powered by"  | Any string                 |
| `branding.poweredByLogo` | String   | No       | Sets your powered by logo                                                                                                          | null          | Valid image link           |
| `allowAudio`             | Boolean  | No       | Sets whether to narrate information to user during tasks                                                                           | `false`       | `true`, `false`            |
| `onClose`                | Function | No       | Callback function that gets triggered when modal is closed                                                                         | null          | Any valid function         |
| `onSuccess`              | Function | No       | Callback function that gets triggered when all tasks have been completed and passed. Called with completion [data](#callback-data) | null          | Any valid function         |
| `onFailure`              | Function | No       | Callback function that gets triggered when at least one task fails. Called with completion [data](#callback-data)                  | null          | Any valid function         |
| `sessionId`              | String   | Yes      | ID generated by your backend using your API key. Validated before SDK init and attached to submissions                             | -             | Any valid session ID       |
| `sessionToken`           | String   | Yes      | Token generated by your backend for liveness verification                                                                          | -             | Any valid session token    |

{% hint style="info" %}
Latest Changes

* The SDK no longer generates session tokens internally.
* Partners must call their backend to generate both sessionId and sessionToken and pass them to the SDK via the respective options.
  {% endhint %}

### Base URL Configuration

All API endpoints use the following base URLs:

* Sandbox base URL:

```
https://api.sandbox.youverify.co
```

* Live base URL:

```
https://api.youverify.co
```

### Session ID generation

Before initializing the SDK, you must generate a sessionId by calling your backend API.

**Endpoint:** `POST /v2/api/identity/sdk/session/generate`

#### Create a request

```kotlin
val request = Request.Builder()
    .url("$baseURL/v2/api/identity/sdk/session/generate")
    .addHeader("Token", apiToken)
    .post(bodyJson.toRequestBody("application/json".toMediaType()))
    .build()
```

#### Create a request body

```kotlin
val bodyJson = JSONObject().apply {
    put("publicMerchantID", publicMerchantId)
    put("metadata", JSONObject()) // whatever metadata you want to pass in
}.toString()
```

#### JSON response

```json
{
  "sessionId": "generated_session_id_here"
}
```

#### Error handling

The `sessionId` should be passed to the SDK constructor.

{% stepper %}
{% step %}

### Complete Integration Flow — Step 1

Generate Session ID: Call your backend to generate `sessionId` using the session generation endpoint.
{% endstep %}

{% step %}

### Complete Integration Flow — Step 2

Generate Session Token: Call your backend to generate `sessionToken` using the liveness token endpoint.
{% endstep %}

{% step %}

### Complete Integration Flow — Step 3

Initialize SDK: Pass both `sessionId` and `sessionToken` to the SDK config.
{% endstep %}

{% step %}

### Complete Integration Flow — Step 4

SDK Validation: The SDK validates the `sessionId` before initialization.
{% endstep %}

{% step %}

### Complete Integration Flow — Step 5

Error Handling: If validation fails, `onFailure` is called with key `invalid_or_expired_session` and `session_token_error` for both.
{% endstep %}

{% step %}

### Complete Integration Flow — Step 6

Success: Upon successful initialization, the SDK uses the `sessionToken` for liveness verification.
{% endstep %}
{% endstepper %}

### Error Keys

<details>

<summary>List of error keys</summary>

* `invalid_or_expired_session`: Returned when the `sessionId` is invalid or expired
* `session_token_error`: Returned when there's an issue with the `sessionToken` during liveness verification

</details>

### Complete Example Implementation

Defining the `LivenessSessionManagement` view model and the \`LivenessSessionRepository:

```kotlin
class LivenessSessionRepository {

    private val client = OkHttpClient()

    suspend fun generateSessionId(
        publicMerchantId: String,
        apiToken: String
    ): String = withContext(Dispatchers.IO) {

        val bodyJson = JSONObject().apply {
            put("publicMerchantID", publicMerchantId)
            put("metadata", JSONObject())
        }.toString()

        val request = Request.Builder()
            .url("https://api.sandbox.youverify.co/v2/api/identity/sdk/session/generate")
            .addHeader("Token", apiToken)
            .post(bodyJson.toRequestBody("application/json".toMediaType()))
            .build()

        val response = client.newCall(request).execute()
        JSONObject(response.body.string()).getString("sessionId")
    }

    suspend fun generateSessionToken(
        publicMerchantId: String,
        deviceCorrelationId: String,
        apiToken: String
    ): String = withContext(Dispatchers.IO) {

        val bodyJson = JSONObject().apply {
            put("publicMerchantID", publicMerchantId)
            put("deviceCorrelationId", deviceCorrelationId)
        }.toString()

        val request = Request.Builder()
            .url("https://api.sandbox.youverify.co/v2/api/identity/sdk/liveness/token")
            .addHeader("Token", apiToken)
            .post(bodyJson.toRequestBody("application/json".toMediaType()))
            .build()

        val response = client.newCall(request).execute()
        JSONObject(response.body.string()).getString("authToken")
    }
}
```

```kotlin
class LivenessSessionManagement(
    private val repo: LivenessSessionRepository = LivenessSessionRepository()
): ViewModel() {
    var sessionId by mutableStateOf<String?>(null)
        private set
    var authToken by mutableStateOf<String?>(null)
        private set
    var isLivenessLoading by mutableStateOf(false)
        private set

    fun load(publicMerchantId: String, deviceCorrelationId: String, apiToken: String) {
        viewModelScope.launch {
            isLivenessLoading = true
            sessionId = null
            authToken = null

            try {
                val id = repo.generateSessionId(publicMerchantId, apiToken)
                val token = repo.generateSessionToken(publicMerchantId, deviceCorrelationId, apiToken)

                sessionId = id
                authToken = token
            } finally {
                isLivenessLoading = false
            }
        }
    }
}
```

Modify `LivenessComponent` to implement these changes.

```kotlin
@Composable
fun LivenessComponent(
    publicKey: String,
    deviceCorrelationId: String,
    apiToken: String,
    sessionViewModel: LivenessSessionManagement = viewModel()
) {
    val sessionId = sessionViewModel.sessionId
    val sessionToken = sessionViewModel.authToken
    val isLivenessLoading = sessionViewModel.isLivenessLoading

    fun initLivenessTokens() {
        sessionViewModel.load(publicKey, deviceCorrelationId, apiToken)
    }

    LaunchedEffect(Unit) {
        initLivenessTokens()
    }

    if (isLivenessLoading || sessionId == null || sessionToken == null) {
        // LoaderUI
        return
    }

    val livenessConfig = YVLivenessConfig(
        sessionId = sessionId,
        sessionToken = sessionToken,
        onSuccess = { data ->
            println("The returned data is $data")
            // Handle success here
        },
        onFailure = { data ->
            println("Liveness check failed with data $data")

            if (data?.error?.key == "invalid_or_expired_session") {
                // Session expired, generate new session and retry
                println("Session expired, retrying...")
                initLivenessTokens()
            } else if (data?.error?.key == "session_token_error") {
                // Session token error, generate new token and retry
                println("Session token error, retrying...")
                initLivenessTokens()
            }
        },
    )

    val livenessController = remember { YVLivenessSDKController(livenessConfig) }

    fun startYesOrNoTask() {
        livenessController.start(tasks = listOf(
            YesOrNoTaskOptions(questions = listOf(
                Question(
                    question = "Is Nigeria a country?",
                    errorMessage = "Read the question more carefully",
                    answer = true
                )
            ))
        ))
    }
    
    Box(
        modifier = Modifier.fillMaxSize(),
        contentAlignment = Alignment.Center
    ) {
        Column {
            Button(onClick = { startYesOrNoTask() }) {
                Text("Start Yes or No Task")
            }
        }

        YVLivenessSDK(livenessController)
    }
}
```

{% hint style="warning" %}
Important Notes

* API Token Security: Never expose your API token in frontend code. All API calls to generate `sessionId` and `sessionToken` should be made from your secure backend.
* Device Correlation ID: The `deviceCorrelationId` should be a unique identifier for the user's device/session. This helps track and prevent abuse.
* Error Handling: Always implement proper error handling for both API calls and SDK initialization to provide a smooth user experience.
* Environment: Use `sandboxEnvironment: true` for testing and `sandboxEnvironment: false` for production.
  {% endhint %}

## Tasks

A task is a series of instructions for users to follow to confirm liveness. Find below a list of tasks.

> PS: We aim to frequently add to this list a variety of fun and yet intuitive ways of confirming liveness, so be on the lookout for more tasks!

### Complete The Circle

User passes task by completing imaginary circle with head movement.

#### CTCTaskOptions

| Option       | Type           | Required | Description                                                    | Default Value         | Possible Values                  |
| ------------ | -------------- | -------- | -------------------------------------------------------------- | --------------------- | -------------------------------- |
| `task`       | Task           | Yes      | Id of task                                                     | -                     | `Task.COMPLETE_THE_CIRCLE`       |
| `difficulty` | TaskDifficulty | No       | Sets difficulty of task                                        | TaskDifficulty.Medium | .Easy, .Medium, .Hard            |
| `timeout`    | Long           | No       | Sets time in milliseconds after which task automatically fails | `20000L`              | Any number in milliseconds(Long) |

### Yes Or No

User passes task by answering a list of arbitrary questions set by you with the tilting of the head; right for a `yes` and left for a `no`.

#### YesOrNoTaskOptions

| Option                   | Type           | Required | Description                                                                            | Default Value         | Possible Values                  |
| ------------------------ | -------------- | -------- | -------------------------------------------------------------------------------------- | --------------------- | -------------------------------- |
| `task`                   | Task           | Yes      | Id of task                                                                             | -                     | `Task.YES_OR_NO`                 |
| `difficulty`             | TaskDifficulty | No       | Sets difficulty of task                                                                | TaskDifficulty.Medium | .Easy, .Medium, .Hard            |
| `timeout`                | Long           | No       | Sets time in milliseconds after which task automatically fails                         | `20000L`              | Any number in milliseconds(Long) |
| `questions`              | List           | No       | A set of questions to ask user                                                         | null                  | See nested options below         |
| `questions.question`     | String         | Yes      | Question to ask user. Should be a `true` or `false` type question. Eg: "Are you ready" | -                     | Any string                       |
| `questions.answer`       | Boolean        | Yes      | Answer to the question                                                                 | -                     | `true`, `false`                  |
| `questions.errorMessage` | String         | No       | Error message to display if user gets question wrong                                   | undefined             | Any string                       |

### Motions

User passes task by performing random motions in random sequences, which include `nodding`, `blinking` and `opening of mouth`.

#### MotionTaskOptions

| Option       | Type           | Required | Description                                                    | Default Value         | Possible Values                  |
| ------------ | -------------- | -------- | -------------------------------------------------------------- | --------------------- | -------------------------------- |
| `task`       | Task           | Yes      | Id of task                                                     | -                     | `Task.MOTIONS`                   |
| `difficulty` | TaskDifficulty | No       | Sets difficulty of task                                        | TaskDifficulty.Medium | .Easy, .Medium, .Hard            |
| `timeout`    | Long           | No       | Sets time in milliseconds after which task automatically fails | `20000L`              | Any number in milliseconds(Long) |
| `maxNods`    | Int            | No       | Maximum amount of nods a user is asked to perform              | 5                     | Any integer                      |
| `maxBlinks`  | Int            | No       | Maximum amount of nods a user is asked to perform              | 5                     | Any integer                      |

### Blink

User passes task by blinking their eyelids based on number of times set.

#### BlinkTaskOptions

| Option       | Type           | Required | Description                                                    | Default Value         | Possible Values                  |
| ------------ | -------------- | -------- | -------------------------------------------------------------- | --------------------- | -------------------------------- |
| `task`       | Task           | Yes      | Id of task                                                     | -                     | `Task.MOTIONS`                   |
| `difficulty` | TaskDifficulty | No       | Sets difficulty of task                                        | TaskDifficulty.Medium | .Easy, .Medium, .Hard            |
| `timeout`    | Long           | No       | Sets time in milliseconds after which task automatically fails | `20000L`              | Any number in milliseconds(Long) |
| `maxBlinks`  | Int            | No       | Maximum amount of nods a user is asked to perform              | 5                     | Any integer                      |

## Callback Data

The `onSuccess` and `onFailure` callbacks (if supplied) are passed the following data:

| Option              | Type              | Description                                                                                                                       |
| ------------------- | ----------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `data`              | LivenessData      | Data passed through callback                                                                                                      |
| `data.faceImage`    | String            | Face Image of user performing liveness check                                                                                      |
| `data.livenessClip` | String            | Video of user performing liveness check                                                                                           |
| `data.passed`       | Boolean           | Indicator on whether liveness check passed or failed                                                                              |
| `data.metadata`     | Map\<String, Any> | Metadata passed in during initialization                                                                                          |
| `data.error`        | LivenessError     | Error object containing error key(`eyes_closed`, `image_capture_failed`, `API_ERROR`) and message (only present in failure cases) |

## Credits

This SDK is developed and maintained solely by [Youverify](https://youverify.co/)


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://doc.youverify.co/know-your-customer-services-kyc/sdk/android-sdk/android-liveness-sdk-new.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
