---
title: Integrating PingOne Recognize on Android
description: Integrate PingOne Recognize biometric enrollment and authentication into Android apps using the Orchestration SDK with PingOne Advanced Identity Cloud journeys
component: orchsdks
page_id: orchsdks:journey:use-cases/recognize/android/index
canonical_url: https://developer.pingidentity.com/orchsdks/journey/use-cases/recognize/android/index.html
llms_txt: https://developer.pingidentity.com/orchsdks/llms.txt
docs_for_agents: https://developer.pingidentity.com/build-with-ai/docs-for-agents.md
revdate: Tue, 08 Sep 2026 14:00:00 +0000
keywords: ["PingOne Recognize", "Biometric", "Face", "Recognition", "Journey", "Integration", "SDK", "Android"]
section_ids:
  keyless: Step 1. Preparing the project to download the dependency
  install: Step 2. Installing the module
  enroll: Step 3. Handling the enroll callback
  authenticate: Step 4. Handling the authenticate callback
  error_handling: Error handling
---

# Integrating PingOne Recognize on Android

[icon: circle-check, set=far]PingOne Advanced Identity Cloud [icon: android, set=fab]Android

The PingOne Recognize module abstracts away the underlying biometric enrollment and authentication protocols, so you can add strong, passwordless authentication to your app with minimal code.

During a journey, the server returns a single callback, `PingOneRecognizeCallback`, and tells your app which ceremony to run using the `operationType` output field:

* `ENROLL`: perform a biometric enrollment ceremony.

* `AUTHENTICATE`: perform a biometric authentication ceremony.

Your app never configures the underlying mobile SDK itself. All configuration, such as the service host and API key, arrives in the callback output from the node.

Before you start, make sure you meet the following requirements:

* Your journeys run in an activity that extends an AndroidX `ComponentActivity`, or another activity implementing `ActivityResultCaller`, so the ceremony can deliver its result to your app.

* You have completed the [server-side setup](https://docs.pingidentity.com/pingoneaic/latest/advanced-use-cases/pingone-recognize-service.html) for PingOne Recognize.

## Step 1. Preparing the project to download the dependency

The `recognize` module depends on the [PingOne Recognize mobile SDK](https://docs.pingidentity.com/recognize/mobile-sdk/mobile-sdk-introduction.html), which Ping distributes from a private Cloudsmith Maven repository. The `recognize` module itself is published to Maven Central, but Gradle cannot resolve its dependency tree, and therefore cannot download the module, until you configure access to that repository.

To configure your project to download the SDK, obtain an API key and add the repository to your project:

1. When your PingOne Recognize tenant has been provisioned, sign in to the dashboard for your region to obtain an API key for the `keyless` repositories:

   * [Europe](https://dash-eu.recognize.keyless.technology/auth)

   * [United States](https://dash-us.recognize.keyless.technology/auth)

   * [Australia](https://dash-au.recognize.keyless.technology/auth)

   * [Singapore](https://dash-sg.recognize.keyless.technology/auth)

2. Add the API key to your user-level Gradle properties file, typically at `~/.gradle/gradle.properties`:

   ```gradle
   cloudsmithTokenRecognize=<your-api-key>
   ```

   |   |                                                                                                                                           |
   | - | ----------------------------------------------------------------------------------------------------------------------------------------- |
   |   | Never commit API keys to source control.Add the key to your local `gradle.properties` file, or inject it as a secret in your CI pipeline. |

3. In the **Project** tree view, open the `Gradle Scripts/settings.gradle.kts` file, and add the `keyless` partner repository. Make sure the variable name in the path matches the property you added in the previous step, and prefix it with a dollar sign, for example, `$cloudsmithTokenRecognize`:

   ```gradle
   dependencyResolutionManagement {
       repositories {
           google()
           mavenCentral()
           maven(url = "https://dl.cloudsmith.io/$cloudsmithTokenRecognize/keyless/partners/maven/")
       }
   }
   ```

## Step 2. Installing the module

Add the `recognize` module to your Orchestration SDK project:

1. In the **Project** tree view of your Android Studio project, open the `Gradle Scripts/build.gradle.kts` file for the *module*.

2. In the `dependencies` section, add the required dependencies:

   ```gradle
   dependencies {
       // Journey module
       implementation("com.pingidentity.sdks:journey:2.1.0")

       // PingOne Recognize module
       implementation("com.pingidentity.sdks:recognize:2.1.0")
   }
   ```

3. Synchronize your Gradle project to download the dependencies.

Gradle downloads the `recognize` module from Maven Central and the PingOne Recognize mobile SDK from the private repository you configured in [Step 1. Preparing the project to download the dependency](#keyless).

|   |                                                                                                                                                                                                                          |
| - | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|   | The callbacks and ceremonies are registered automatically using [AndroidX App Startup](https://developer.android.com/topic/libraries/app-startup), so you don't need to initialize anything in your `Application` class. |

## Step 3. Handling the enroll callback

The server triggers an enrollment ceremony by returning a `PingOneRecognizeCallback` with `operationType = "ENROLL"`.

The Orchestration SDK resolves the callback from the output fields, so your app only needs to identify the enroll callback and call `enroll()` on it:

```kotlin
if (node is ContinueNode) {
  node.callbacks.forEach { callback ->
    if (callback is PingOneRecognizeEnrollCallback) {
      val result = callback.enroll()
      when {
        result.isSuccess -> {
          // Enrollment successful, proceed to the next step in the journey
          await continueNode.next()
        }
        result.isFailure -> {
          // Enrollment failed, handle the error; see Error handling
          val error = result.exceptionOrNull()
          println("Enrollment failed: code=${(error as RecognizeException).code}, message=${error.message}")
          await continueNode.next()
        }
      }
    }
  }
}
```

Optionally, set the `retrieveSelfie` option to `true` to receive the image captured during the ceremony on the result:

```kotlin
val result = callback.enroll {
  retrieveSelfie = true
}

when {
  result.isSuccess -> {
    val selfie: Bitmap? = result.getOrNull()?.selfie
    // Enrollment successful, proceed to the next step in the journey
    await continueNode.next()
  }
  result.isFailure -> {
    val error = result.exceptionOrNull()
    println("Enrollment failed: code=${(error as RecognizeException).code}, message=${error.message}")
    // Continue the journey to report client errors back to the server
    await continueNode.next()
  }
}
```

By default, the Orchestration SDK does not return the captured image.

## Step 4. Handling the authenticate callback

The server requests a biometric authentication ceremony by returning a `PingOneRecognizeCallback` with `operationType = "AUTHENTICATE"`.

To authenticate an enrolled user, call `authenticate()` on the resolved callback:

```kotlin
if (node is ContinueNode) {
  node.callbacks.forEach { callback ->
    if (callback is PingOneRecognizeAuthenticateCallback) {
      val result = callback.authenticate()
      when {
        result.isSuccess -> {
          // Authentication successful, proceed to the next step in the journey
          await continueNode.next()
        }
        result.isFailure -> {
          // Authentication failed, handle the error; see Error handling
          val error = result.exceptionOrNull()
          println("Authentication failed: code=${(error as RecognizeException).code}, message=${error.message}")
          await continueNode.next()
        }
      }
    }
  }
}
```

The callbacks submit their inputs to the journey automatically. Do not call `input()` manually, or you risk submitting a duplicate result.

On success, the SDK submits the signed JWT, current user ID, client state, and freshly retrieved device public signing key to the server automatically.

|   |                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| - | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|   | If the server sends a value in the `clientState` output field, such as when the node enables the Generate Client State property, and the device is not yet enrolled, then the device is enrolled automatically during the authentication ceremony.The Orchestration SDK passes the client state to the PingOne Recognize mobile SDK, which uses the client state to restore the user's account on the device.The Orchestration SDK handles this fallback itself, so no extra code is needed in your app. |

## Error handling

All Recognize SDK failures are wrapped in a `RecognizeException` before they reach your code:

```kotlin
class RecognizeException(
    val code: Int,                          // Numeric code from the underlying mobile SDK
    override val message: String,           // Human-readable description
    val debuggingInfo: Map<String, String>, // Diagnostic entries, such as the ceremony flow ID, session ID, and stack trace
    cause: Throwable? = null,
) : Exception(message, cause)
```

The `code` and `message` values are injected into the `clientError` and `clientErrorCode` input slots. You must call `continueNode.next()` after encountering an error so your server receives the error detail, regardless of how your app handles the failed result.

The `isFailure` branch receives the failed result as a `Throwable`, so check its type before reading the error detail. PingOne Recognize SDK failures arrive as `RecognizeException`, but other failures, such as a network error, can arrive as any other exception type, for example:

```kotlin
val result = callback.authenticate()
when {
  result.isSuccess -> {
    // Authentication successful, proceed to the next step in the journey
    await continueNode.next()
  }
  result.isFailure -> {
    val error = result.exceptionOrNull()
    when (error) {
      is RecognizeException -> {
        when (error.code) {
          30003 -> showRetryPrompt("Face scan cancelled")       // User cancelled
          30004 -> showRetryPrompt("Face did not match")        // Face not matching
          30005 -> showRetryPrompt("No network connection")     // No network connection
          30007 -> showLockoutScreen()                          // User lockout
          30009 -> requestCameraPermission()                    // Camera permission denied
          else -> println("Other Recognize error: code=${error.code}, message=${error.message}")
        }
      }
      else -> println("Other exception: ${error.message}")
    }
    // Continue the journey to report client errors back to the server
    await continueNode.next()
  }
}
```

The codes in the example are in the user error range, 30000 and above, which result from user behavior during the ceremony, such as cancelling the face scan or failing the biometric match.

Codes below 30000 indicate integration or internal errors, such as `20000` when the user is not yet enrolled on the device.

The final `else` branch catches any exception that is not a `RecognizeException`, such as a network error, so your app never crashes on an unexpected failure type.
