Document Verification

Verifies the identity of the user using documents like their driver's license or passport

Description

This step uses Mosaic's hosted experience to validate the user's identity using documents they provide (see Supported documents). For example, a banking app can validate a user's name and ID for a new account opening, or validate the identity of a first-time app user before sending them their login credentials.

Once invoked, the step will redirect the user to the Mosaic verification experience. This page will perform a verification process that collects user consent, captures document images and a selfie of the user, and redirects back to your configured callback URL once all the verification checks are processed. After your callback page resumes the journey, the step will proceed to obtain the verification result. Journey execution will resume with the relevant branch based on the recommendation returned in the result (either allow, challenge, or deny).

If defined, an output variable will store the verification result (including document details and personal user info - see example here). This data can be used in subsequent journey steps in the expression fields (e.g., identity_result.verified_info.document.type) or as a part of an interpolated string (e.g., Hi ${identity_result.verified_info.person.given_name}).

If needed, you can test the journey by configuring mock behavior for the step. For example, you can simulate a flow that results in a specific recommendation without performing an actual verification.

Document verification is provided by Mosaic identity verification. For Web, no additional configuration is needed. For mobile apps, use a dedicated SDK to invoke document verification: Identity Verification (iOS) or Identity Verification (Android).

Note for mobile developers
  • Make sure the Identity verification SDK is configured to work in the same region as other Mosaic services.
  • For details on how to set up and initialize the Identity verification SDK for mobile apps, see Steps 2 & 3 of Android quickstart or iOS quickstart .

Configuration

Field Description
Time to live Time in seconds after which the verification session data will be deleted. Default is 90 days (in seconds).
Callback URL* URL that the user will be redirected to once the verification process completes.
Output Variable* Name of the variable used to store the verification result data, which can be used in subsequent journey steps. Default is identity_result. This corresponds to an object with the structure described here and includes document details and personal user info.
Session Error Behavior Determines the behavior in case an unexpected error occurs during the verification process. You can either choose to abort the journey (default) or proceed to a dedicated error branch.
Mocking Behavior Enabled If enabled, the step will be performed using mock behavior for testing purposes only. For example, you can simulate a flow that results in a specific recommendation without performing an actual verification.
Recommendation The desired mock recommendation for this session (see recommendations)
Processing Time Desired processing time of the mock verification (in seconds) once all the images are captured.
Force Recapture Allows simulating a recapture status for the mock flow. If Yes, a recapture status will be returned for the first attempt to verify the session

Example

In our example, the user tries to get their identity verified by submitting their documents for verification. This step redirects the user to the Mosaic verification experience and obtains a recommendation. For example, proceed normally if 'allow', terminate if 'deny', or enforce a manual review procedure if 'challenge'.

Configuring document verification step
Click to open the image in a dedicated tab.

When executed, this step sends a callback to the client with the IDO service response object. It will have the journeyStepId set to identityVerification and the data will include the endpoint to redirect to.

Copy
Copied
{
 "data": {
   "payload": {
     "endpoint": "<endpoint to redirect>",
     "base_endpoint": "<base endpoint>",
     "start_token": "<start token>",
     "state": "<state>",
     "session": "<session>"
     },
   }
}

Upon extracting the values from data, the client needs to save the state of the IDO SDK by calling the serialize state function (see IDO SDK for Web) and, for example, saving the state in the local storage. Once the user completes verification, the Mosaic verification experience redirects back to the client that loads the state in order to resume the journey (see IDO SDK for Web). The client then processes incoming query parameters and submits the response to the journey using the IDO SDK call.

JavaScriptSwiftKotlin
Copy
Copied
// On page load
const params = Object.fromEntries(new URLSearchParams(window.location.search).entries());
const sdkState = loadFromLocalStorage(); // Implementation of this function is up to the developer
if (params.state && params.sessionId && sdkState) {
  // Indicates the journey resumes after redirection
  const idoResponse = window.tsPlatform.ido.restoreFromSerializedState(sdkState);
  if (idoResponse.journeyStepId === IdoJourneyActionType.IdentityVerification) {
    handleIdentityVerification(idoResponse);
  } else { /* Handle this */ }
} else { // Indicates a regular journey flow
  // TODO: Init IDO DK, start journey, and loop+switch on IdoJourneyActionType
  //
}

// Handle the Document verification step
async function IdentityVerification(idoResponse) {
  const params = Object.fromEntries(new URLSearchParams(window.location.search).entries());

  if (params.state && params.sessionId) {
    // Mosaic verification experience returns params
    // Submit the data to the SDK
    const data = {
      payload: {
        state: params.state,
        sessionId: params.sessionId,
      },
    };

    await window.tsPlatform.ido.submitClientResponse(ClientResponseOptionType.ClientInput, data);
  } else {
    // If the sessionID and state are not in the URL, redirect the user to the Mosaic verification experience
    const sdkState = window.tsPlatform.ido.serializeState();
    await saveToLocalStorage(sdkState); // implementation of this function is up to the developer

    window.location.href = idoResponse.data?.payload.endpoint;
  }
}
Copy
Copied
// Initialize IDV SDK
private func initializeIdvSDK() {
    do {
        try TSIdentityVerification.initializeSDK()
        TSIdentityVerification.delegate = self
    } catch {
        debugPrint("[DEBUG]: initialization error: \(error)")
    }
}
// TSIdoDelegate TSIdoDidReceiveResult function implementation
func TSIdoDidReceiveResult(_ result: Result<TSIdoServiceResponse, TSIdoJourneyError>) {
    switch result {
    case .success(let response):
        self.handleIdoResponse(response)
    case .failure(let error):
        // Handle error
    }
}
private func handleIdoResponse(_ response: TSIdoServiceResponse) {
    guard let stepId = response.journeyStepId else {
        debugPrint("[DEBUG] Missing step id in the journey response")
        return
    }

    if stepId == .identityVerification {
        guard let payload = response.data["payload"] as? [String: Any],
              let startToken = payload["start_token"] as? String {
                  debugPrint("[DEBUG] Start token is missing")
                  return
              }

        initializeIdvSDK()

        requestCameraPermissionsAndStartSDK(startToken: startToken)
    }
}

// Before starting the IDV, requesting camera permissions is required
private func requestCameraPermissionsAndStartSDK(startToken: String) {
    switch AVCaptureDevice.authorizationStatus(for: .video) {
    case .authorized: // the user has already authorized to access the camera
        self.startIdv(startToken: startToken)

    case .notDetermined: // the user has not yet asked for camera access
        AVCaptureDevice.requestAccess(for: .video) { (granted) in
            if granted { // if user has granted to access the camera
                self.startIdv(startToken: startToken)
            } else {
                // Handle this case
            }
        }

    case .denied, .restricted:
        // Handle this case
    @unknown default:
        // Handle this case
    }
}

// Start IDV SDK
private func startIdv(startToken: String) {
    TSIdentityVerification.start(startToken: startToken)
}

// TSIdentityVerificationDelegate functions implementation
extension ViewController: TSIdentityVerificationDelegate {
    func verificationDidComplete() {
        debugPrint("[DEBUG]: verification process did complete")

        // Submit parameters to IDO SDK
        TSIdo.submitClientResponse(clientResponseOptionId: .clientInput)
    }

    func verificationDidFail(with error: IdentityVerification.TSIdentityVerificationError) {
        debugPrint("[DEBUG]: Verification process did fail with error: \(error)")

        // Submit failure to IDO SDK
        TSIdo.submitClientResponse(clientResponseOptionId: .fail)
    }
}
Copy
Copied
private fun processServiceResponse(idoResponse: TSIdoServiceResponse) {
    when(idoResponse.journeyStepId) {
        ...
        TSIdoJourneyActionType.IdentityVerification.type -> handleIdentityVerification(idoResponse)
        ...
    }
}

private fun handleIdentityVerification(
        result: TSIdoServiceResponse
    ) {
            // Check if the camera permission is granted
                if (ContextCompat.checkSelfPermission(context, android.Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
                    // Extract the start_token and base_endpoint from IDO response
                    val idvStartToken = (result.data as JSONObject).optJSONObject("payload").optString("start_token")
                    val idvBaseEndpoint = (result.data as JSONObject).optJSONObject("payload").optString("base_endpoint")

                    // Start the identity verification process using the IDV SDK
                    TSIdentityVerification.initialize(context, CLIENT_ID, idvBaseEndpoint)
                    TSIdentityVerification.registerForStatus(this) //"this" implements ITSIdentityVerificationStatus interface
                    TSIdentityVerification.start(it, idvStartToken)
                } else {
                    // Request camera permission
                    ActivityCompat.requestPermissions(activity, arrayOf(android.Manifest.permission.CAMERA),
                        101
                    )
                }
    }
// This method is implemented by the listener that implements the ITSIdentityVerificationStatus interface
override fun verificationCompleted() {
        val session = (idoResponse.data as JSONObject).optJSONObject("payload").optString("session")
        val state = (idoResponse.data as JSONObject).optJSONObject("payload").optString("state")
        val payload = IDVResponsePayload(session, state)
        // Submit response to IDO SDK
        TSIdo.submitClientResponse(TSIdoClientResponseOptionType.ClientInput.type, IDVResult(payload), callback)

    }
...
data class IDVResponsePayload(val sessionId: String, val state: String)
data class IDVResult(val payload: IDVResponsePayload)