On-Device Age Estimation

On-Device Age Estimation

On-Device Age Estimation runs the face-analysis pipeline - face detection, positioning, liveness, and age estimation - entirely on the user's device during Face Capture (the SelfieScan module). On this path, no selfie image, video, or biometric frames are uploaded to Incode. Only structured analysis results (for example: liveness, face quality, and the estimated age) are submitted to the Incode backend. On-device processing requires E2EE: the SDK enforces it for the session and encrypts the submitted metadata client-side.

Because the biometric capture never leaves the device, the feature also skips the normal selfie image upload, video/frame recording upload, and manual capture. Age decisioning is performed on the backend from the submitted results.

📘

Availability

On-Device Age Estimation is available on Android Welcome SDK 5.47.0 and later, in STANDARD mode only. It requires using UX V2, the SelfieScan module in SelfieScan.Mode.ENROLL, and is not supported for Face Login.

Prerequisites

This guide assumes the Welcome SDK is already installed and initializing in your app. If you are starting from scratch, complete Android SDK Setup (GitHub Packages, dependencies, permissions) and the SDK Integration guide first, then return here.

Before you enable On-Device Age Estimation, make sure the following are in place:

  • Android Welcome SDK 5.47.0 or later in SdkMode.STANDARD.
  • The two on-device model dependencies added to your app's module build.gradle / build.gradle.kts. These are optional for the Welcome SDK overall, but required for On-Device Age Estimation:
dependencies {
    // Required for On-Device Age Estimation
    implementation 'com.incode.sdk:model-liveness-detection'
    implementation 'com.incode.sdk:model-age-estimation'
}
dependencies {
    // Required for On-Device Age Estimation
    implementation("com.incode.sdk:model-liveness-detection")
    implementation("com.incode.sdk:model-age-estimation")
}

Versions are managed by the Incode BOM. See the BOM Version Mapping for the versions bundled with each SDK release. If either dependency is missing while the feature is enabled, SelfieScan.Builder.build() throws MissingModelLivenessDetectionDependencyException or MissingModelAgeEstimationDependencyException.

  • E2EE enabled for your organization and a valid E2EE URL. End-to-end encryption is required for on-device processing. If E2EE is not enabled on the session, the flow ends with OnDeviceProcessingNotCompatibleException (reason MISSING_E2EE). See the E2EE Integration Guide for the full setup.
  • A Flow configured in the Incode Dashboard with an accessible Flow ID (used as the configurationId on the session) if you use the server-driven startFlow() path.
  • No conflicting capture routing. Frame streaming and screen recording must be disabled on this path, because both would send frames off-device. See Error handling for the exact conditions.
  • The V2 (Compose) selfie UI. On-device processing runs only on the V2 selfie UI; a flow routed through the legacy V1 UI raises OnDeviceProcessingNotCompatibleException (reason V1_NOT_SUPPORTED). See Enable the V2 UI for how to turn it on per start method.
📘

Minimum SDK

The model-age-estimation module targets the SDK's mandatory minSdk 23 and ships the same ABIs as the rest of the SDK, so it adds no new device or hardware gate for this feature. See Android SDK Setup for the supported ABIs and base packaging.

Configure On-Device Processing

For server-driven flows, log in to the Dashboard to enable On-Device Processing on the Face Capture module configuration in your Flow or Workflow.

Server-driven Flows and Workflows (recommended)

  1. In the left menu, click Flow Builder > Flows OR Workflows.
  2. Open your Flow or Workflow.
  3. Open the configuration panel for the Face Capture module.
  4. Enable On-Device Processing. This ensures that liveness and age estimation run entirely on the user's device. No biometric images leave the SDK.
  5. If using Workflows, click Save configurations. Flow configurations save automatically.
  6. Once the configuration panel closes, if using Workflows, click Save & Publish. If using Flows, click Save Changes.

The SDK reads this setting from the flow automatically. There is no code flag to set.

Client-configured Flows (alternative)

If you build your own FlowConfig in code and start the flow with startOnboarding(), set the flag explicitly with SelfieScan.Builder().setOnDeviceFaceResultsSubmissionEnabled(true) (see step 3 below). The flag defaults to false.

Enable the V2 UI

On-device age estimation runs only on the V2 (Compose) selfie UI. A Flow on the legacy V1 UI raises OnDeviceProcessingNotCompatibleException (reason V1_NOT_SUPPORTED). Turn V2 on the way you would for any V2 integration. For the full mechanism and the ECSM caveat, see Enabling the V2 UI in the SDK Customization guide.

Server-driven Flows and Workflows (recommended)

  1. In the left menu, click Flow Builder > Flows OR Workflows.
  2. Open your Flow or Workflow configured for on-device processing.
  3. Click the Settings tab.
  4. Toggle Enable new user experience on.
  5. For Flows, click Save Changes. For Workflows, click Save & Publish.

Client-configured Flows (alternative)

Enable V2 explicitly at initialization with IncodeWelcome.Builder.setClientExperimentId("experimentV2") (see step 1 below).

Integration steps

1. Initialize the SDK

Initialize the Welcome SDK, supplying your custom E2EE server URL as the fourth constructor argument, then obtain the singleton instance.

IncodeWelcome.Builder(this, WELCOME_API_URL, WELCOME_API_KEY, E2EE_URL)
    // Opts client-configured flows (startOnboarding) into UX V2 - see "Enabling the V2 UI".
    .setClientExperimentId("experimentV2")
    .build()

val incodeWelcome = IncodeWelcome.getInstance()
new IncodeWelcome.Builder(this, WELCOME_API_URL, WELCOME_API_KEY, E2EE_URL)
    // Opts client-configured flows (startOnboarding) into UX V2 - see "Enabling the V2 UI".
    .setClientExperimentId("experimentV2")
    .build();

IncodeWelcome incodeWelcome = IncodeWelcome.getInstance();

2. Create the session configuration

Create a SessionConfig that references your Dashboard Flow ID and enables E2EE. The configurationId is identical to the Dashboard flow ID (use "Copy flow ID" in the Dashboard).

val sessionConfig: SessionConfig = SessionConfig.Builder()
    .setConfigurationId(FLOW_ID)
    .setE2eEncryptionEnabled(true)
    .build()
SessionConfig sessionConfig = new SessionConfig.Builder()
    .setConfigurationId(FLOW_ID)
    .setE2eEncryptionEnabled(true)
    .build();

Because setE2eEncryptionEnabled() is a likely target for fraudsters, protect this code against reverse engineering. See the E2EE Integration Guide for the full hardening guidance.

3. Start the flow

Unlike iOS, Android does not use a presenting view controller. You pass an Android Context and the SDK launches its own activities.

Server-driven flow (recommended). With on-device processing enabled in the Dashboard, no code flag is needed. Start the flow with startFlow():

IncodeWelcome.getInstance().startFlow(
    activityContext,
    sessionConfig,
    onboardingListener)
IncodeWelcome.getInstance().startFlow(
    activityContext,
    sessionConfig,
    onboardingListener);

Server-driven workflow. Workflows are also server-driven. Enable "On-Device Processing" on the Face Capture step of your Workflow configuration in the Dashboard, set the session's configurationId to the Workflow ID, and start it with startWorkflow(). No code flag is needed:

IncodeWelcome.getInstance().startWorkflow(
    activityContext,
    sessionConfig,
    onboardingListener)
IncodeWelcome.getInstance().startWorkflow(
    activityContext,
    sessionConfig,
    onboardingListener);

Client-configured flow (alternative). If you build the FlowConfig in code, enable the feature on the SelfieScan module and start the flow with startOnboarding():

val flowConfig: FlowConfig = FlowConfig.Builder()
    // ... other modules
    .addSelfieScan(
        SelfieScan.Builder()
            .setOnDeviceFaceResultsSubmissionEnabled(true)
            .build())
    .build()

IncodeWelcome.getInstance().startOnboarding(
    activityContext,
    sessionConfig,
    flowConfig,
    onboardingListener)
FlowConfig flowConfig = new FlowConfig.Builder()
    // ... other modules
    .addSelfieScan(
        new SelfieScan.Builder()
            .setOnDeviceFaceResultsSubmissionEnabled(true)
            .build())
    .build();

IncodeWelcome.getInstance().startOnboarding(
    activityContext,
    sessionConfig,
    flowConfig,
    onboardingListener);

The SDK validates on-device compatibility at flow start on every path. If any requirement is not met, the flow ends via OnboardingListener.onError(...) (see Error handling).

4. Handle the result

The SelfieScanResult is delivered to your OnboardingListener through onSelfieScanCompleted(selfieScanResult). Errors are delivered asynchronously to onError(error).

val onboardingListener = object : IncodeWelcome.OnboardingListener {
    // ... other flow callbacks

    override fun onSelfieScanCompleted(selfieScanResult: SelfieScanResult) {
        // The selfie image and video were never uploaded to Incode on this path.
        // Continue your flow using the structured result.
    }

    override fun onError(error: Throwable) {
        if (error is OnDeviceProcessingNotCompatibleException) {
            // A requirement for on-device processing was not met.
        }
    }
}
IncodeWelcome.OnboardingListener onboardingListener = new IncodeWelcome.OnboardingListener() {
    // ... other flow callbacks

    @Override
    public void onSelfieScanCompleted(SelfieScanResult selfieScanResult) {
        // The selfie image and video were never uploaded to Incode on this path.
        // Continue your flow using the structured result.
    }

    @Override
    public void onError(Throwable error) {
        if (error instanceof OnDeviceProcessingNotCompatibleException) {
            // A requirement for on-device processing was not met.
        }
    }
};
📘

Result on the on-device path

Because no server-side face evaluation runs on this path, SelfieScanResult.isSpoofAttempt stays null. The estimated age is computed on-device and submitted to Incode as part of the structured results; it is not returned in SelfieScanResult.

The SelfieScanResult still contains the locally captured selfie image (selfieBase64 / fullFrameSelfieBase64, and the local croppedSelfieImgPath / fullFrameSelfieImgPath). The on-device guarantee is that these image bytes are never uploaded to Incode - not that the local result is image-less. Handle them on-device according to your own privacy requirements.

Compatible configuration matrix

On-device age estimation only works in a narrow, privacy-preserving configuration. Unusual Flow / Workflow / FlowConfig combinations that are valid for a normal Face Capture will abort the session when on-device processing is enabled. Use this matrix when configuring any on-device age-estimation integration.

SettingRequired valueWhere to set itIf wrong
SDK modeSdkMode.STANDARDApp initFeature unavailable
Welcome SDK version5.47.0+App dependencies / BOMFeature unavailable
Model artifactsmodel-liveness-detection and model-age-estimation presentApp build.gradleMissingModel*DependencyException at SelfieScan.Builder.build()
E2EEEnabled on the session + valid E2EE URL at SDK initSessionConfig.setE2eEncryptionEnabled(true) + 4-arg IncodeWelcome.BuilderOnDeviceProcessingNotCompatibleException (MISSING_E2EE)
On-Device ProcessingOn for the Face Capture moduleDashboard Face Capture config, or SelfieScan.Builder().setOnDeviceFaceResultsSubmissionEnabled(true) for client-built flowsOn-device path not entered
UX V2On ("Enable new user experience")Dashboard Flow/Workflow Settings, or setClientExperimentId("experimentV2") for client-built flowsOnDeviceProcessingNotCompatibleException (V1_NOT_SUPPORTED)
Selfie modeSelfieScan.Mode.ENROLLFace Capture / SelfieScan configFace Login / auth modes are not supported
Frame streaming / "Enable face recording"OffDashboard Face Capture, or do not call setStreamFramesEnabled(true)OnDeviceProcessingNotCompatibleException (FRAME_STREAMING)
Screen recordingOffFlowConfig.Builder.configureScreenRecording(false, ...)OnDeviceProcessingNotCompatibleException (SCREEN_RECORDING)
Manual captureDisabled by the SDK on this pathN/A (not configurable when on-device is on)Manual-capture UI / timeout fallbacks do not apply; keep auto-capture healthy
Start pathstartFlow / startWorkflow (recommended) or startOnboarding with the flag aboveApp codeSame compatibility checks run on every path
📘

Expected differences vs a normal Face Capture

On this path the SDK does not upload the selfie image or video, does not expose manual capture, and does not run server-side face evaluation (SelfieScanResult.isSpoofAttempt stays null). Server-side checks that depend on a stored selfie (for example Face Recognition / Face Match scores) may return N/A even if those modules appear later in the same Flow or Workflow. Configure age decisioning from the on-device structured results your backend receives, not from selfie-dependent scores.

Error handling

Two distinct failures can occur when the feature is misconfigured:

  • MissingModelLivenessDetectionDependencyException / MissingModelAgeEstimationDependencyException - thrown synchronously from SelfieScan.Builder.build() when the feature is enabled but the required optional model dependency is absent. Fix by adding the dependencies from Prerequisites.
  • OnDeviceProcessingNotCompatibleException - delivered asynchronously to OnboardingListener.onError(...) at flow start when the configuration is incompatible. It carries one of the following reasons:
ReasonMeaningFix
MISSING_E2EEE2EE is not enabled on the session.Call SessionConfig.Builder.setE2eEncryptionEnabled(true).
FRAME_STREAMINGFrame streaming is enabled on the same SelfieScan.Disable frame streaming (do not call setStreamFramesEnabled(true) / face recording).
SCREEN_RECORDINGScreen recording is configured for the flow.Call FlowConfig.Builder.configureScreenRecording(false, ...).
V1_NOT_SUPPORTEDThe flow routes through the legacy V1 selfie UI (for example Face Login).Use the V2 selfie UI in enrollment mode.

Related guides


Did this page help you?