Verify documents using Android SDK

You can use identity verification to securely verify the identity of your customers using documents like their driver’s license or passport—such as before allowing them to open a new bank account online or pick up a rental car. This guide describes how to quickly integrate identity verification into your Android application using our Android SDK, including both the client-side and backend integration.

How it works

Here's an example of a basic integration flow. Transmit APIs are shown in pink along with the relevant integration step.

After initializing the SDK (Step 3), your app starts a verification flow by creating a session in the backend to establish a secure context (Step 6 and Step 7) and then starting the session (Step 8). The SDK executes the verification process with the user using the Transmit identity verification experience. Once all the required images are submitted, Transmit starts processing the verification while the SDK polls for its status. Once processing is completed, the SDK notifies the app (Step 4) so it can obtain the verification result (Step 9) and proceed accordingly (Step 10).

Step 1: Configure your app

To integrate with Transmit, you'll need to configure an application.

From the Applications page, create a new application or use an existing one. From the application settings:

  • For Client type , select native
  • For Redirect URI , enter your website URL. This is a mandatory field, but it isn't used for this flow.
  • Obtain your client ID and secret for API calls, which are autogenerated upon app creation.

Step 2: Add SDK to project

Add the following lines in the shared build.gradle file ("allprojects" scope):

Copy
Copied
dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {

        maven {
            url('https://transmit.jfrog.io/artifactory/transmit-security-gradle-release-local/')
        }
        mavenCentral()
        google()
    }
}

Add the following in the module build.gradle file (project scope):

Copy
Copied
dependencies {
    implementation("com.ts.sdk:identityverification:1.0.+")
}

Step 3: Initialize the SDK

Configure the SDK using one of the snippets below, where CLIENT_ID is your client ID (obtained in Step 1):

KotlinJava
Copy
Copied
class MyApplication: Application() {
    override fun onCreate() {
        super.onCreate()
        TSIdentityVerification.initialize(this, "CLIENT_ID")
    }
}
Copy
Copied
public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        TSIdentityVerification.initialize(this, "CLIENT_ID")

    }
}
Note

The SDK can be configured to work with an EU cluster by setting the third initialization parameter to baseUrl : 'https://api.eu.transmitsecurity.io/'.

Step 4: Observe status updates

Once a verification process has been initiated (as described in Step 8), the verification moves through different statuses. For example, the status will indicate if the process was completed successfully so the app can fetch the verification result. To observe status changes, first register for the status by calling registerForStatus on onStart and then implement the ITSIdentityVerificationStatus shown below on the MainActivity class of your app.

Here are examples of adding the registerForStatus and ITSIdentityVerificationStatus to your Activity class:

KotlinJava
Copy
Copied
class MainActivity : AppCompatActivity() {
    override fun onStart() {
        super.onStart()
        TSIdentityVerification.registerForStatus(this)
    }
}
Copy
Copied
public class MainActivity extends AppCompatActivity{

    @Override
    protected void onStart() {
        super.onStart();
        TSIdentityVerification.registerForStatus(this);
    }
}

And now implement the ITSIdentityVerificationStatus on your Activity class:

KotlinJava
Copy
Copied
class MainActivity : AppCompatActivity(), ITSIdentityVerificationStatus {
    override fun onStart() {
        super.onStart()
        TSIdentityVerification.registerForStatus(this)

    }

    // Notifies when user has started to capture images.
    override fun verificationStartCapturing() {

        Log.d("verificationDidReceiveStatus", "verificationCapturing")
    }

    // Notifies when user has finished uploading images and the verification is being processed.
    override fun verificationStartProcessing() {

        Log.d("verificationDidReceiveStatus", "verificationStartProcessing")
    }

    // Notifies when verification process couldn't be completed, but the user can try again. (Step 11)
    override fun verificationRequiresRecapture(reason: TSRecaptureReason) {

        Log.d("verificationDidReceiveStatus", reason.name)
    }

    // Notifies when verification process completed, and the result can be obtained (via backend request).
    //(Step 9)
    override fun verificationCompleted() {

        Log.d("verificationDidReceiveStatus", "verificationCompleted")
    }

    // Notifies when user has canceled the process.
    override fun verificationCanceled() {

    Log.d("verificationDidReceiveStatus", "verificationCanceled")
    }

    // Notifies when verification error occurs.
    override fun verificationFail(error: TSIdentityVerificationError) {

        Log.d("verificationDidFail", error.name)
    }
}
Copy
Copied
public class MainActivity extends AppCompatActivity implements ITSIdentityVerificationStatus {

    @Override
    protected void onStart() {
        super.onStart();
        TSIdentityVerification.registerForStatus(this);
    }

    // Notifies when user has started to capture images.
    @Override
    public void verificationStartCapturing() {
        Log.d("verificationDidReceiveStatus", "verificationCapturing");
    }

    // Notifies when user has finished uploading images and the verification is being processed.
    @Override
    public void verificationStartProcessing() {
        Log.d("verificationDidReceiveStatus", "verificationStartProcessing");
    }

    // Notifies when verification process couldn't be completed, but the user can try again. (Step 11)
    @Override
    public void verificationRequiresRecapture(TSRecaptureReason reason) {
        Log.d("verificationDidReceiveStatus", reason.name);
    }

    // Notifies when verification process completed, and the result can be obtained (via backend request).
    //(Step 9)
    @Override
    public void verificationCompleted() {
        Log.d("verificationDidReceiveStatus", "verificationCompleted");
    }

    // Notifies when user has canceled the process.
    @Override
    public void verificationCanceled() {

    Log.d("verificationDidReceiveStatus", "verificationCanceled")
    }

    // Notifies when verification error occurs.
    @Override
    public void verificationFail(TSIdentityVerificationError error) {
        Log.d("verificationDidFail", error.name());
    }
}

Step 5: Add camera permission

Your app requires camera permissions in order to capture the images required for the verification process.

  1. Open the AndroidManifest.xml file located in the app directory of your Android project.
  2. Add the following permission inside the <manifest> tag:
Copy
Copied
<uses-permission android:name="android.permission.CAMERA" />
  1. To handle camera permissions, add the following code snippet to the activity or fragment where you need to request camera permissions:
KotlinJava
Copy
Copied
// Check if the camera permission is granted
if (ContextCompat.checkSelfPermission(this, android.Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
    // Camera permission granted, start Identity Verification here
} else {
    // Request camera permission
    ActivityCompat.requestPermissions(this, arrayOf(android.Manifest.permission.CAMERA), 101)
}

// Handle the result of the permission request
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
    if (requestCode == 101) {
        if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // Camera permission granted, start Identity Verification here
        } else {
            // Camera permission denied, handle unauthorized state
        }
    }
}
Copy
Copied
// Check if the camera permission is granted
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
    // Camera permission granted, start Identity Verification here
} else {
    // Request camera permission
    ActivityCompat.requestPermissions(this, new String[]{android.Manifest.permission.CAMERA}, 101);
}

// Handle the result of the permission request
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
    if (requestCode == 101) {
        if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
            // Camera permission granted, start Identity Verification here
        } else {
            // Camera permission denied, handle unauthorized state
        }
    }
}

Step 6: Get access tokens

An access token is required to authorize the backend API calls, such as for creating a verification session (Step 7) and obtaining the result (Step 9). When needed, obtain an access token using the /token request below.

Copy
Copied
import fetch from 'node-fetch';

async function run() {
  const formData = {
    client_id: '[CLIENT_ID]', // Replace with client ID obtained in Step 1
    client_secret: '[CLIENT_SECRET]', // Replace with client secret obtained in Step 1
    grant_type: 'client_credentials',
    resource: 'https://verify.identity.security'
  };

  const resp = await fetch(
    `https://api.transmitsecurity.io/oidc/token`, // Use api.eu.transmitsecurity.io for EU clusters
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded'
      },
      body: new URLSearchParams(formData).toString()
    }
  );

  const data = await resp.text();
  console.log(data);
}

run();
Notes
  • The token must be requested for the https://verify.identity.security resource, which will appear in the audience claim of the generated token (in the future we’ll block access to tokens without this audience).
  • The token must remain secure on your server, and must only be used for backend requests.

Step 7: Create session

Before your mobile app can initiate the verification process, your backend must create a session in order to provide a secure context for the flow. Create a session by sending the /verification request below.

Copy
Copied
import fetch from 'node-fetch';

async function run() {
  const resp = await fetch(
    `https://api.transmitsecurity.io/verify/api/v1/verification`, // Use api.eu.transmitsecurity.io for EU clusters
    {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: 'Bearer [ACCESS_TOKEN]' // Replace with access token obtained in Step 6
      }
    }
  );

  const data = await resp.json();
  console.log(data);
}

run();

The response contains a start_token that will be used to start the verification on the client side (in Step 8), and the session_id required to obtain the verification result (in Step 9). For example:

Copy
Copied
{
  "start_token": "ca766ed78c8c0b7824dfea356ed30b72",
  "session_id": "H1I12oskjzsdhskj4",
  "expiration": "2023-07-18T09:57:46.950Z",
  "missing_images": [
    "document_front",
    "document_back",
    "selfie"
  ]
}

Step 8: Start session

Once a session is created, initiate the verification process using the start() SDK method. Add the code below to your mobile app, passing the start_token value returned in the previous step. If successful, the SDK will start a verification process for the user and guide them through the entire identity verification flow using the Transmit experience.

KotlinJava
Copy
Copied
TSIdentityVerification.start(context, "START_TOKEN")
Copy
Copied
TSIdentityVerification.start(context, "START_TOKEN");

Step 9: Get verification result

Once the verification process starts, your mobile app can track its status using the extension added in Step 4.

After all the required images are successfully submitted, Transmit automatically starts processing the verification and the SDK starts polling to check the status. If the status is completed, your backend should send the request below to obtain the verification result (see API reference):

Copy
Copied
import fetch from 'node-fetch';

async function run() {
  const sid = '[SESSION_ID]'; // Replace with session ID returned in Step 7
  const resp = await fetch(
    `https://api.transmitsecurity.io/verify/api/v1/verification/${sid}/result`, // Use api.eu.transmitsecurity.io for EU clusters
    {
      method: 'GET',
      headers: {
        Authorization: 'Bearer [ACCESS_TOKEN]' // Replace with access token obtained in Step 6
      }
    }
  );

  const data = await resp.text();
  console.log(data);
}

run();
Note

See Step 11 for how to handle the recapture status, in case it's returned.

Step 10: Handle verification result

Your app should define the user experience based on the overall verification result, provided by the recommendation field:

  • If ALLOW : the identity verification process was completed successfully. The response includes user details collected from the document (like their name and birthday), which can be used to enrich the user's profile, and details about the document used to prove their identity.
  • If CHALLENGE : the identity verification process didn’t succeed, since at least one verification check didn’t pass. The response includes extracted (yet unverified) info and indicates which checks didn't pass and why. This info can be used to review unsuccessful sessions or analyze failed verification attempts. You should proceed as suitable for your use case, typically by initiating a manual review process.
  • If DENY : the identity verification indicates a high likelihood of attempted fraud. The response includes the extracted (yet unverified) info and indicates which checks didn't pass and why. You should block the user or initiate an in-depth manual review to avoid onboarding a fraudulent user.

Collected information is arranged inside nested objects: person, document, and additional_info.

Besides the composite verification result, the response provides information about individual checks inside the nested checks object.

Here's a response example for successful ID verification:

Copy
Copied
{
  "session_id": "H1I12oskjzsdhskj4",
  "status": "complete",
  "recommendation": "ALLOW",
  "person": {
    "full_name": "Marie Salomea Skłodowska-Curies",
    "given_name": "Marie",
    "surname": "Curies",
    "national_id": "123ABC",
    "date_of_birth": "1867-11-07T00:00:00.000Z"
  },
  "document": {
    "country": "US",
    "region": "NY",
    "type": "national_id",
    "number": "1234567",
    "serial_number": "1234567",
    "issue_date": "1867-11-07T00:00:00.000Z",
    "expiration_date": "1867-11-07T00:00:00.000Z"
  },
  "additional_info": {
    "address": {
      "country": "USA",
      "region": "Indiana",
      "city": "Indianapolis",
      "street": "Snowy Ridge Road",
      "house_number": "1234",
      "apartment_number": "12",
      "postcode": "56789",
      "full_address": "1234 Snowy Ridge Road Indianapolis, IN 56789"
    },
    "national_status": {
      "citizen": true,
      "resident": true
    },
    "employment": {
      "profession": ""
    }
  },
  "checks": {
    "document_validation": {
      "recommendation": "ALLOW"
    },
    "document_authentication": {
      "recommendation": "ALLOW"
    },
    "document_liveness": {
      "recommendation": "ALLOW"
    },
    "biometric_matching": {
      "recommendation": "ALLOW"
    },
    "biometric_liveness": {
      "recommendation": "ALLOW"
    },
    "flagged_identity": {
      "recommendation": "ALLOW"
    },
    "risk_recommendation": {
      "recommendation": "ALLOW"
    }
  }
}

The response indicates when checks fail and why:

Copy
Copied
{
  "session_id": "string",
  "status": "complete",
  "recommendation": "DENY",
  // EXTRACTED DATA...
  "checks": {
    "document_validation": {
      "recommendation": "ALLOW"
    },
    "document_authentication": {
      "recommendation": "ALLOW"
    },
    "document_liveness": {
      "recommendation": "DENY",
      "reasons": [ "presentation_attack" ]
    },
    "biometric_matching": {
      "recommendation": "CHALLENGE",
      "reasons": [ "biometric_mismatch" ]
    },
    "biometric_liveness": {
      "recommendation": "DENY", 
      "reasons": [ "mask detected" ]
    },
    "flagged_identity": {
      "recommendation": "DENY",
      "reasons": [ "multiple_same_identity_attempts" ]
    },
    "risk_recommendation": {
      "recommendation": "DENY",
      "reasons": [ "action_is_suspected_fraud" ]
    }
  }
}

Step 11: Recapture images

The recapture status is returned in certain cases, such as if some data couldn't be extracted due to poor image quality. When this happens, your app can allow the user to try again by calling the recapture() SDK call, which will recapture only the required images. Once the SDK submits the required images, the processing status will be returned until the processing is completed. The app can then fetch the verification result as described in Step 9.

KotlinJava
Copy
Copied
TSIdentityVerification.recapture(context)
Copy
Copied
TSIdentityVerification.recapture(context);