Skip to content

Integration

Architecture overview

To access the XpressID service, Veridas will provide you with the following credentials:

  • XpressID_URL: This URL varies depending on the environment (sandbox: https://xpressid-web-work.eu.veri-das.com or production: https://xpressid-web.eu.veri-das.com), region, and other factors. Veridas will provide you with the appropriate URL based on your needs. Use this URL within an iframe element for seamless integration.
  • XpressID_API_URL: This URL also varies depending on the environment and region (sandbox: https://api-work.eu.veri-das.com/xpressid or production: https://api.eu.veri-das.com/xpressid). It points directly to the XpressID application, specifically for the authentication API. Veridas will provide you with this URL as well.
  • API_KEY: This is a unique identifier for your client account. Keep this API key confidential and secure as it grants access to your XpressID services through the XpressID_API_URL.

To ensure compliance with Veridas' security protocols, clients must provide information regarding the server IPs where XpressID integration is planned. It's important to note that this security measure is exclusive to production environments and does not apply to the sandbox.

Achieving a functional integration of the XpressID service within a customer's infrastructure necessitates interaction with two key elements.

Authentication API

To obtain the access tokens necessary for the XpressID iFrame, it is imperative that requests be initiated exclusively from your backend system. These requests should encompass your onboarding flow configuration. The API_KEY value is particularly crucial in this context. For enhanced security, it is strongly advised to implement precautionary measures, including incorporating a login step before accessing XpressID. Additionally, introducing a captcha step to mitigate potential abuse and enforcing a rate limit mechanism to prevent multiple requests from the same IP address are recommended practices.

Moreover, it is advisable to integrate Distributed Denial of Service (DDoS) mitigation measures to fortify the system against potential malicious attacks. These collective measures contribute to a robust security framework, safeguarding the XpressID integration and ensuring a secure and reliable operation of the service within your application's ecosystem.

Iframe

In this context, XpressID Web is seamlessly integrated into the application as an iframe, using both the XpressID_URL and the ACCESS_TOKEN obtained during the authentication API step. The HTML code for XpressID is designed for frontend implementation. However, to maintain security, refrain from including the API_KEY variable in the frontend. Manage the API_KEY exclusively within the backend, where robust security measures can be implemented to mitigate the risk of exposing confidential data through the frontend. A valid XpressID_URL and access token are essential for a successful launch, emphasizing the need for secure backend handling during the authentication process.

The recommended architecture of a solution with a web application using XpressID service should follow next diagram.

Alt

Number Description
1 The user logs in within your fronted application. he user accesses your web application by authenticating within your application
2 Your backend application requests a token from XpressID Auth API using the XpressID_API_URL and the API_KEY. You can check how the request is made here
3 XpressID Auth API returns the response for the token request.
4 Your backend application processes the response from the token request to return the ACCESS_TOKEN to your web application.
5 Your frontend application will use the XpressID_URL and the ACCESS_TOKEN to launch the XpressID iFrame. You can see how the web integration works here.
6 Throughout the XpressID process, events are returned to your web application that need to be listened to and handled appropriately. You can seehere the events that the iFrame is sending to you.

Iframe integration

Once the access token is obtained, the iframe can be embedded into the HTML code in this way:

<iframe
    id="XpressID-iframe"
    allow="camera; microphone; geolocation;"
    src="https://XpressID_URL/v2/?access_token=ACCESS_TOKEN">
</iframe>

Another example, the iframe can be embedded into the REACT code in this way:

const render = messages => {
    ReactDOM.render(
        <Provider store={store}>
            <LanguageProvider messages={messages}>
                <ConnectedRouter history={history}>
                <iframe style="height:500px; width:500px;" src="https://XpressID_URL/v2?access_token=ACCESS_TOKEN"></iframe> <App />
                </ConnectedRouter>
            </LanguageProvider>
        </Provider>,
    MOUNT_NODE,
    );
};

Note that since it’s an iframe, there is no problem related to CORS (cross-origin resource sharing) and the XpressID URL can be embedded directly into the iframe itself.

The iFrame's src property must be updated when it becomes visible to ensure proper loading with correct dimensions. If the src is set before the iFrame is visible, the content might not render correctly or might appear distorted. This is because the iFrame's size might not be known until it's displayed on the page.

This is the only needed code in order to make XpressID to work properly. Note that ACCESS_TOKEN is unique for each final user and it’s going to be different every time XpressID is embedded.

The next point is an optional step because it depends on the use case, it consists of adjusting the iframe to screen size.

If you want to adjust the iframe size so that it takes up the whole screen, it’s a good practice to set the width and the height of the iframe to 100%. This works quite well, however, sometimes it’s necessary to do an additional step because some browsers (such as Safari mobile) may experience problems.

Moreover, it’s recommended to control the resize/rotation of the screen with an "EventListener". So to do this, we propose this cross-browser and easy-to-implement solution:

<html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
    </head>
    <body>
        <iframe
            id="XpressID-iframe"
            allow="camera; microphone; geolocation;"
            style="width:100%;height:100%;"
            src="https://XpressID_URL/v2?access_token=ACCESS_TOKEN">
        </iframe>
        <script type="text/javascript">
            (function() {
                adjustIframeDimensions();
                addEventListener('resize', adjustIframeDimensions);
            })();
            function adjustIframeDimensions() {
                var browser = navigator.userAgent.toLowerCase();
                if (browser.indexOf('safari')>-1 && browser.indexOf('mobile')>-1){ document.getElementsByTagName('html')[0].style.height = '100vh'; setTimeout(() => {
                    document.getElementsByTagName('html')[0].style.height = '100%'; }, 500);
                }
            }
        </script>
    </body>
</html>

Receiving events from the iFrame

When the process is over, a postMessage is sent to the parent website where it has been embedded. If desired, it is possible to listen to that message with an event listener:

window.addEventListener("message", receiveMessage, false);
function receiveMessage(event) {
    if (event.origin.includes("xpressid")) {
        var dataFromXpressID = event.data;
        if(event.data.code == "ProcessCompleted") {
            // do something -> destroy iframe, redirect to another page, etc.
        }
        else if (event.data.code == "ProcessCancelled") {
            // do something -> destroy iframe, redirect to another page, show a message, etc.
        }
        else if (event.data.code == "ProcessSigned") {
            // do something -> save the additionalData to request a signed PDF after this
        }
        else if (event.data.code == "ProcessSignatureRejected") {
            // do something -> save the additionalData to log, redirect to another page, show a message, etc.
        }
        else if (event.data.code == "ProcessEvents") {
            // do something -> log events, etc.
        }
        else if(event.data.type == "error") {
            // do something ->  destroy iframe, redirect to another page, show a message, retry process with another token, etc.
        }
    }
}

For correct use of events the event listener must be invoked prior to loading XpressID. We recommend first adding the listener to the iframe first and then replacing the src property of the iframe with the value of https://XpressID_URL/v2?access_token=ACCESS_TOKEN.

Throughout the process, needs to send some data to the parent website where it’s embedded. This data can be of two types:

  • Informational data
  • Error data

Note that with the "event.origin.includes("xpressid")" directive we ensure the event comes from XpressID, otherwise, the event could be coming from another part of the website.

The returned data ("dataFromXpressID" in the example), will always have the same structure: it’s a JavaScript object with these fields:

Name Req. Type Description
code yes string unique event code
type yes string event type. Only two available values: "info" (for informational data) and "error" (for error data)
message yes string event text explanation
stage no string stage where the event has taken place
infoEvent no object additional data that only exists in some certain events

To distinguish between the different messages, it is necessary to check the "event.data.code" and "event.data.type" fields.

Informational data

This is the event that send informational data to the parent website where XpressID is embedded.

ProcessCompleted

This event occurs when the process has just been completed successfully. The structure of the received data is:

{
    type: "info",
    code: "ProcessCompleted",
    message: "The process has been successfully completed",
}
ProcessCancelled

This event occurs when the process has been cancelled by the user. The structure of the received data is:

{
    type: "info",
    code: "ProcessCancelled",
    message: "The process has been canceled",
}
ProcessSigned

This event occurs when the signature process has just been completed successfully. The structure of the received data is:

{
    type: "info",
    code: "ProcessSigned",
    message: "Sign process has been successfully completed.",
    additionalData: {
      "signatureRequestId": SIGNATURE_REQUEST_ID,
      "signatureId": SIGNATURE_ID,
      "nameOrInitials": NAME_OR_INITIALS_WHO_SIGN
    }
}
ProcessSignatureRejected

This event occurs when the signing process is rejected by the end user. The structure of the received data is:

{
    type: "info",
    code: "ProcessSignatureRejected",
    message: "Sign process has been refused.",
    additionalData: {
      "signatureRequestId": SIGNATURE_REQUEST_ID,
      "signatureId": SIGNATURE_ID,
      "nameOrInitials": NAME_OR_INITIALS_WHO_SIGN
    }
}
ProcessEvents

This event occurs throughout the process and provides information about what is happening:

{
        type: "info",
        code: "ProcessEvents",
        message: "Information about events during the process",
        infoEvent: {
            code: "DocumentProcessStarted",
            detail: "Document capture process has been started.",

       }
}

In the following tables appears all the events are provided by XpressID during the process.

QR
infoEvent.code infoEvent.detail
QRProcessStarted QR process started.
QRContinueOnDesktop Clicked the continue button on the desktop.
QRProcessFinished QR process finished.
QRProcessCaptured QR process captured.
QRClosedModal Closed the QR modal.
QRProcessOnboardingError QR process onboarding error.
QRProcessCancelled QR process cancelled.
QRProcessCompleted QR process completed.
Document & country selector
infoEvent.code infoEvent.detail
SelectorProcessStarted Started document selector by country.
ClickOnDocumentSelector Clicked any document option of the selector.
SelectorProcessFinished Finished document selector by country.
Autoclassification selector
infoEvent.code infoEvent.detail
ACSelectorProcessStarted Started autoclassification selector.
ClickOnDocumentACSelector Clicked one of the optioncs of the acselector.
ACSelectorProcessFinished Finished autoclassification selector.
Document capture
infoEvent.code infoEvent.detail
DocumentProcessStarted Document capture process has been started.
DocumentMounted Document capture process has been prepared.
DocumentUnmounted Document capture process has been finished.
DocumentRestartProcess The document capture process needs to restart because an error occurred
DocumentProcessFinished Document capture process has been finished.
DocumentCameraStarted The camera has been started in the document process.
DocumentCameraVideoPlayStarted The camera is launched correctly on document process.
DocumentObverseDetection The obverse of the document has been captured.
DocumentReverseDetection The reverse of the document has been captured.
DocumentManualCapture The document has been captured manually.
DocumentSuccessTickFinish The success tick is fired correctly on document process.
DocumentOrientationChanged The document capture orientation has changed.
DocumentRepeatClicked The document capture process has been repeated.
DocumentRestartClicked The user has clicked the restart button in the document process.
DocumentDependenciesError There was a problem loading the dependencies during the document process.
DocumentMountFailure Document capture process initialization has failed.
DocumentCameraFailure There was a problem starting the camera in the document process.
DocumentDetectionTimeout Timeout has been exceeded.
DocumentCloseButtonClicked The close button has been clicked in the document process.
DocumentProcessRestarted The process has been restarted in the document process.
DocumentOrientationStyleChanged Orientation styles has changed in the document process
DocumentReviewImage Review image has been displayed in the document process.
Selfie capture
infoEvent.code infoEvent.detail
SelfieProcessStarted Selfie capture process has been started.
SelfieMounted Selfie capture process has been prepared.
SelfieUnmounted Selfie capture process has been finished.
SelfieRestartProcess The selfie capture process has been restart because an error occurred.
SelfieProcessFinished Selfie capture process has been finished.
SelfieCameraStarted The camera has been started in the selfie process.
SelfieCameraFailure There was a problem starting the camera in the selfie process.
SelfieRepeatClicked The selfie capture process has been repeated.
SelfieFaceDetection Face has been successfully captured in selfie process.
SelfieCameraVideoPlayStarted The camera is launched correctly on selfie process.
SelfieCameraRecorderStop There was a problem starting the camera in the selfie process.
SelfieRestartToken New challenge is generated during the selfie process
SelfieSuccessTickFinish The success tick is fired correctly on selfie process
SelfieRestartClicked The user has clicked the restart button in the selfie process.
SelfieDetectionTimeout Timeout has been exceeded in selfie process.
SelfieOrientationChanged The selfie capture orientation has changed.
SelfieMountFailure Selfie capture process initialization has failed.
SelfieDependenciesError There was a problem loading the dependencies during the selfie process.
SelfieCameraRecorderSetup The camera has started recording in the selfie process.
SelfieCameraRecorderSetupError An error has been found during the camera setup of the selfie process.
SelfieCloseButtonClicked Close button has been clicked in the selfie process.
SelfieContinueClicked The user has clicked the continue button in image review screen during the selfie process.
SelfieProcessRestarted The selfie capture process has been restarted
SelfieOrientationStyleChanged Orientation styles has changed in the selfie process
SelfieReviewImage Review image has been displayed in the selfie process.
Video capture
infoEvent.code infoEvent.detail
VideoProcessStarted Video capture process has been started.
VideoMounted Video capture process has been prepared.
VideoUnmounted Video capture process has been finished.
VideoProcessFinished Video capture process has been finished.
VideoRestartProcess The video capture process has been restart because an error occurred.
VideoCameraStarted The camera has been started in the video process.
VideoCaptureDetection The video capture process has been started.
VideoCameraVideoPlayStarted The camera is launched correctly on video process.
VideoCameraRecorderStop The camera has been stopped in the video process.
VideoSuccessTickFinish The success tick is fired correctly on video process.
VideoRestartClicked The user has clicked the restart button in the video process.
VideoOrientationChanged The video capture orientation has changed.
VideoMountFailure Video capture process initialization has failed.
VideoDependenciesError There was a problem loading the dependencies during the video process.
VideoDetectionTimeout Timeout has been exceeded in video process.
VideoCameraFailure There was a problem starting the camera in the video process.
VideoCameraRecorderSetup The camera has started recording in the video process.
VideoCloseButtonClicked Close button has been clicked in the video process.
VideoProcessRestarted The video capture process has been restarted
VideoOrientationStyleChanged Orientation styles has changed in the video process
Esign process
infoEvent.code infoEvent.detail
EsignProcessStarted Esign process has been started.
EsignRefused Clicked the refuse button to cancel the signature.
EsignSigned Clicked the sign button to confirm the signature.
EsignProcessFinished Esign process has been finished.

Error data

When an error occurs, XpressID tries to handle it so that it is transparent to the user. However, there are certain errors which can not be handled by XpressID. In these cases, it will display an error message to the user, and send a message to the parent website via postMessage.

Note that although the error code is immutable, some error messages can be customized.

Here below there is a list with all unhandled errors.

All errors are of type: "error", and the detailed information is in the event code and message.

These events occur when the process fails either because the document or the selfie or video could not be processed, preventing the end user from continuing.

Importantly, these types of events must be captured in order to reload the Iframe with a new token and thus a new validation.

The stage property indicates at which point the process has failed. It can represent the values of document, selfie ,video and connection (when the service fails).

For example, when the token has expired and the iFrame XpressID is started, it returns the event of type "error" with the detailed message that the token has expired.

{
    type: "error",
    code: "TokenNotAllowed",
    message: "Token has been expired"
}

Another example, this error occurs when any service fails. The structure of the received data is:

{
    type: "error",
    code: "ConnectionError",
    message: "There has been a problem with the connection"
}

Errors that may occur in the analysis of the process (uploading of documents, selfie, video) are also added.

code message Causes of
ValidationError "The provided data is not valid" This error appears when any configuration of input request is incorrect. For example, a document type that does not
ErrorProcessingRequest "Processing previous request" This error ocurrs when the previous image is being processed and has not been completed. i.e. Sending the reverse when the obverse has not finished.
ErrorUpdateValidation "Validation deleted during processing" This error ocurrs if the validation is deleted while the process is still active
UnknownDocumentTypeError "Document Analysis error: The provided image could not be classified. Please verify that the image and the document type are correct." This error occurs when the classification method can not classify the document in image under the configuration conditions of document type. For example if a real ES_IDCard_2015 is sent ValiDas under document_type MX. The error is generic and occurs when using the "PUT document ".
"Obverse Document Analysis error: The provided image could not be classified. Please verify that the image and the document type are correct." This error occurs when the classification method can not classify the document in image under the configuration conditions of document type. For example, if a real ES_IDCard_2015 is sent ValiDas under document_type MX. This error is returned when all the documents have been sent in the POST, and the obverse has failed.
UnknownAnalysisTypeError "Unknown analysis type" "analysisType" parameter is not correctly definied. "analysisType" should be "obverse" or "reverse"
InvalidData Invalid json data The JSON data is not valid.
ScoresConfigurationNotAllowed "ScoresConfiguration field is just allowed in obverse analysis" "ScoresConfiguration" field is just allowed in obverse analysis"
ScoresConfigurationError "Scores configuration has an incorrect value" This error occurs when the configuration levers contain any lever with an incorrect value. For example if configuration contains "ScoreGroup-PhotoAuthenticity": 2.5
InvalidScoresConfigurationField ScoresConfiguration field contains invalid JSON ScoresConfiguration field contains invalid JSON
UnallowedLeversError "Document Analysis error: The following levers could not be found please check for errors" This error occurs when the configuration levers contain any lever that does not exist or misspelled. For example if configuration contains "ScoreGroup-PhotoAAuthenticity": 1. The error is generic and occurs when using the "PUT document ".
"Obverse Document Analysis error: The following levers could not be found please check for errors" This error occurs when the configuration levers contain any lever that does not exist or misspelled. For example if configuration contains "ScoreGroup-PhotoAAuthenticity": 1 .This error is returned when all the documents have been sent in the POST and the obverse has failed.
DocumentAnalysisError "Error analyzing document" Error analyzing document
EmptyDocumentError "Empty document file" The document file uploaded is empty.
InvalidImageType "Invalid image type" The image type is not correct
ImageAlreadyAnalyzedError "The provided image type has already been uploaded and analyzed" This error occurs when a process of an image type already exists. For example, if an image with reverse configuration is sent twice.
ErrorProcessingFile "The file couldn't be processed properly" This error occurs when the document file that is sent cannot be processed correctly, and therefore, Veridas cannot analyze it.
DocumentServiceNotReachable Service url not set Veridas Internal connectivity error
DocumentNfcUnavailableError "Document Analysis error: NFC is not available for this document type" This error occurs when a document type does not contain NFC. For example if you send NFC configuration for a document type ES_ResidencePermit_2010. The error is generic and occurs when using the "PUT document".
"Obverse Document Analysis error: NFC is not available for this document type" This error occurs when a document type does not contain NFC. For example, if you send NFC configuration for a document type ES_ResidencePermit_2010. This error is returned when all the documents have been sent in the POST, and the obverse has failed.
BlurredImageError "Document Analysis error: The provided image does not meet the minimum quality" This error occurs when image quality is bad and blurr of image is high. The error is generic and occurs when using the "PUT document".
"Obverse Document Analysis error: The provided image does not meet the minimum quality" This error occurs when Obverse quality is bad and blurr of image is high. This error is returned when all the documents have been sent in the POST, and the obverse has failed.
"Reverse Document Analysis error: The provided image does not meet the minimum quality" This error occurs when Reverse quality is bad and blurr of image is high. This error is returned when all the documents have been sent in the POST, and the reverse has failed.
AdditionalDocumentProcessError "Main document not analyzed" This error is related to "multiple onboarding". The main document has not been analyzed.
"Previous additional document not uploaded" This error is related to "multiple onboarding". The previous additional document has not be uploaded
"You have already processed 3 additional documents" This error is related to "multiple onboarding". 3 documents has already been processed.
"The process for this additional document has already finished" This error is related to "multiple onboarding". The process for an additional document has already finished
"Obverse has not been uploaded for this additional document" This error is related to "multiple onboarding". Obverse has not been uploaded for an additional document.
DocumentAnalyzingError "Document Analysis error: The provided image has given an error while analyzing" This error occurs when main process of document analysis has stopped. This is cause by different types of processing errors. The error is generic and occurs when using the "PUT document".
"Obverse Document Analysis error: The provided image has given an error while analyzing" This error occurs when main process of document analysis has stopped. This is cause by different types of processing errors. This error is returned when all the documents have been sent in the POST, and the obverse has failed.
"Reverse Document Analysis error: The provided image has given an error while analyzing" This error occurs when main process of document analysis has stopped. This is cause by different types of processing errors. This error is returned when all the documents have been sent in the POST, and the reverse has failed.
DocumentDetectionTimeout Timeout has been exceeded. The error occurs when there is user inactivity and the process terminates due to inactivity.
SelfieDetectionTimeout Timeout has been exceeded. The error occurs when there is user inactivity and the process terminates due to inactivity.
VideoDetectionTimeout Timeout has been exceeded. The error occurs when there is user inactivity and the process terminates due to inactivity.
DocumentMountFailure Document capture process initialization has failed This error occurs when the process fails to start due to some compatibility issues with the browser.
SelfieMountFailure Selfie capture process initialization has failed This error occurs when the process fails to start due to some compatibility issues with the browser.
VideoMountFailure Video capture process initialization has failed This error occurs when the process fails to start due to some compatibility issues with the browser.

For example, when an image that does not correspond to the document type.

{
    type: "error",
    code: "UnknownDocumentTypeError",
    message: "Document Analysis error: The provided image could not be classified. Please verify that the image and the document type are correct.",
    stage: "document"
}

Webviews Integration

XpressID supports webviews, allowing processes to be conducted within the webviews of Instagram, Meta, and native applications on iOS and Android. The following sections provide examples of how to implement these integrations for each platform.

To integrate XpressID in a webview, there is no need to use an iframe tag, as the webview itself functions like an iframe.

Note: It is essential to instruct users to select "Always Allow" for camera permissions when using Meta webview on Android, as some devices do not behave correctly.

Native iOS Webview Integration

To integrate XpressID in a native iOS webview, you need to set up the required permissions and configure WKWebView to load the desired URL. Below are the steps to achieve this:

Required Permissions

Ensure that your application requests the necessary permissions by adding the following keys to your Info.plist file:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
        <key>CFBundleDevelopmentRegion</key>
        <string>$(DEVELOPMENT_LANGUAGE)</string>
        <key>CFBundleDisplayName</key>
        <string>WebViews</string>
        <key>CFBundleExecutable</key>
        <string>$(EXECUTABLE_NAME)</string>
        <key>CFBundleIdentifier</key>
        <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
        <key>CFBundleInfoDictionaryVersion</key>
        <string>6.0</string>
        <key>CFBundleName</key>
        <string>$(PRODUCT_NAME)</string>
        <key>CFBundlePackageType</key>
        <string>APPL</string>
        <key>CFBundleShortVersionString</key>
        <string>$(MARKETING_VERSION)</string>
        <key>CFBundleVersion</key>
        <string>$(CURRENT_PROJECT_VERSION)</string>
        <key>LSRequiresIPhoneOS</key>
        <true/>
        <key>NSAppTransportSecurity</key>
        <dict>
            <key>NSAllowsArbitraryLoads</key>
            <true/>
        </dict>
        <key>NSCameraUsageDescription</key>
        <string>adasd</string>
        <key>NSMicrophoneUsageDescription</key>
        <string>ddsads</string>
        <key>UILaunchStoryboardName</key>
        <string>LaunchScreen</string>
        <key>UIMainStoryboardFile</key>
        <string>Main</string>
        <key>UIRequiredDeviceCapabilities</key>
        <array>
            <string>armv7</string>
        </array>
        <key>UISupportedInterfaceOrientations</key>
        <array>
            <string>UIInterfaceOrientationPortrait</string>
            <string>UIInterfaceOrientationLandscapeLeft</string>
            <string>UIInterfaceOrientationLandscapeRight</string>
        </array>
        <key>UISupportedInterfaceOrientations~ipad</key>
        <array>
            <string>UIInterfaceOrientationPortrait</string>
            <string>UIInterfaceOrientationPortraitUpsideDown</string>
            <string>UIInterfaceOrientationLandscapeLeft</string>
            <string>UIInterfaceOrientationLandscapeRight</string>
        </array>
    </dict>
    </plist>
WebViewController

Here is an example of a WebViewController class in iOS that sets up a WKWebView, handles permissions, and listens for events.

This WebViewController class in iOS sets up a WKWebView to load a specified URL, handling JavaScript execution, permissions, and event listening.

It ensures WebView debugging, JavaScript, and inline media playback are enabled. The class injects JavaScript to listen for message events and forwards these messages to the native iOS interface using a JavaScript bridge.

    // Inject JavaScript code into the web view
        let script = """
        (function() {
            window.parent.addEventListener('message', function(event) {
                window.webkit.messageHandlers.\(jsMessageHandler).postMessage(JSON.stringify(event.data));
            });
        })();
        """
        let scriptInjection = WKUserScript(source: script, injectionTime: .atDocumentEnd, forMainFrameOnly: false)
        contentController.addUserScript(scriptInjection)

        webViewConf.userContentController = contentController
    // MARK: - WKScriptMessageHandler
    func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
        // Handle the message received from the web page
        print("Received message from web page: \(message.body)")
    }

The class handles permissions for accessing the camera and microphone by requesting the necessary permissions if not already granted. Additionally, it provides methods for navigating back and forward in the WebView's history, and it includes a search bar for loading specific URLs.

    import UIKit
    import WebKit

    final class WebViewController: UIViewController {

        // MARK: - Private
        private var webView: WKWebView!
        private let refreshControl = UIRefreshControl()

        // MARK: - Public
        public var baseUrl: String = ""

        override func viewDidLoad() {
            super.viewDidLoad()

            // Web view
            let webViewPrefs = WKPreferences()
            webViewPrefs.javaScriptEnabled = true
            webViewPrefs.javaScriptCanOpenWindowsAutomatically = true

            let webViewConf = WKWebViewConfiguration()
            webViewConf.preferences = webViewPrefs
            webViewConf.allowsInlineMediaPlayback = true

            // Add JavaScript bridge
            let contentController = WKUserContentController()
            contentController.add(self, name: "jsHandler")
            webViewConf.userContentController = contentController

            webView = WKWebView(frame: view.frame, configuration: webViewConf)
            webView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
            webView.scrollView.keyboardDismissMode = .onDrag
            view.addSubview(webView)
            webView.navigationDelegate = self

            // Refresh control
            refreshControl.addTarget(self, action: #selector(reload), for: .valueChanged)
            webView.scrollView.addSubview(refreshControl)
            view.bringSubviewToFront(refreshControl)

            // Load url
            load(url: baseUrl)
        }

        // MARK: - Private methods

        private func load(url: String) {
            if let url = URL(string: url) {
                webView.load(URLRequest(url: url))
            }
        }

        @objc private func reload() {
            webView.reload()
        }
    }

    // MARK: - WKNavigationDelegate

    extension WebViewController: WKNavigationDelegate {

        func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
            refreshControl.endRefreshing()
        }

        func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
            refreshControl.beginRefreshing()
        }

        func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
            refreshControl.endRefreshing()
        }
    }

    // MARK: - WKScriptMessageHandler

    extension WebViewController: WKScriptMessageHandler {
        func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
            if message.name == "jsHandler", let messageBody = message.body as? String {
                // Handle the received message from JavaScript here
                print("JavaScript message received: \(messageBody)")
            }
        }
    }

Native Android Webview Integration

To integrate XpressID in a native Android WebView, you need to set up the required permissions and configure the WebView to load the desired URL. Below are the steps to achieve this:

Required Permissions

Add Permissions in the Manifest.xml

    <?xml version="1.0" encoding="utf-8"?>
    <manifest xmlns:android="http://schemas.android.com/apk/res/android">


    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.CAMERA" />
    <uses-feature android:name="android.hardware.camera" />
    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
    <uses-permission android:name="android.webkit.PermissionRequest" />
    <uses-permission android:name="android.webkit.resource.VIDEO_CAPTURE" />
    <uses-permission android:name="android.webkit.resource.AUDIO_CAPTURE" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />


    <application
        android:name=".MainApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".ConfigurationActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".MainActivity"
            android:configChanges="orientation|screenSize"
            android:exported="true">
        </activity>
    </application>
    </manifest>
MainActivity

Here is an example of MainActivity code that sets up the WebView, requests permissions, and handles event listening.

This MainActivity class in Android sets up a WebView to load a specified URL, handling JavaScript execution, permissions, and event listening.

It first ensures WebView debugging, JavaScript, and DOM storage are enabled. It injects JavaScript to listen for message events and forwards these messages to the native Android interface using Android.receiveMessage.

The WebChromeClient handles permissions for accessing the camera and microphone by granting any requested permissions.

Before loading the URL, it checks and requests the necessary camera and microphone permissions if not already granted. Additionally, it binds a JavaScript interface (JsInterface) to facilitate communication between the WebView and the native Android code.

    package com.example.webview

    import android.Manifest
    import android.annotation.SuppressLint
    import android.content.Context
    import android.content.Intent
    import android.content.pm.PackageManager
    import android.os.Bundle
    import android.webkit.PermissionRequest
    import android.webkit.WebChromeClient
    import android.webkit.WebView
    import android.webkit.WebViewClient
    import androidx.appcompat.app.AppCompatActivity
    import androidx.core.app.ActivityCompat
    import androidx.core.content.ContextCompat
    import com.example.webview.databinding.ActivityMainBinding


    class MainActivity : AppCompatActivity() {


    companion object {
        private const val CAMERA_PERMISSIONS_REQUEST_CODE = 12345


        fun start(context: Context) {
            val intent = Intent(context, MainActivity::class.java)
            context.startActivity(intent)
        }
    }


    private lateinit var binding: ActivityMainBinding


    @SuppressLint("SetJavaScriptEnabled")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)


        WebView.setWebContentsDebuggingEnabled(true)
        binding.webView.settings.javaScriptEnabled = true
        binding.webView.settings.domStorageEnabled = true


        binding.webView.webViewClient = object: WebViewClient() {
            override fun onPageFinished(view: WebView?, url: String?) {
                binding.webView.loadUrl(
                        "javascript:(function() {" +
                                "window.parent.addEventListener('message', function(event) {" +
                                " Android.receiveMessage(JSON.stringify(event.data));" +
                                "});" +
                                "})()"
                )
            }
        }


        binding.webView.webChromeClient = object : WebChromeClient() {
            override fun onPermissionRequest(request: PermissionRequest) {
                request.grant(request.resources)
            }
        }


        val cameraPermission = ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
        if (cameraPermission == PackageManager.PERMISSION_GRANTED) {
            binding.webView.loadUrl("https://xpressid-url.com")
        } else {
            requestPermissions()
        }


        binding.webView.addJavascriptInterface(JsInterface(), "Android")
    }


    private fun requestPermissions() {
        ActivityCompat.requestPermissions(
                this,
                arrayOf(
                        Manifest.permission.CAMERA,
                        Manifest.permission.RECORD_AUDIO
                ),
                CAMERA_PERMISSIONS_REQUEST_CODE
        )
    }


    override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults)
        if (requestCode == CAMERA_PERMISSIONS_REQUEST_CODE && grantResults.isNotEmpty()) {
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                binding.webView.loadUrl("https://xpressid-url.com")
            }
        }
    }
    }
JavaScript Interface

Add a JavaScript interface (JsInterface) to allow communication between the web page and the Android native code.

    package com.example.webview


    import android.util.Log
    import android.webkit.JavascriptInterface


    class JsInterface {
    @JavascriptInterface
    fun receiveMessage(data: String): Boolean {
        Log.d("Data from JS", data)
        return true
    }


    }

Recommendations

This section includes general recommendations that ease integration.

How to redirect onboardings from desktop to mobile using QR redirection

This recommendation aims to explain how to include a typical QR redirection process into an integration.

As explained in QR setup section, XpressID provides some configuration parameters that enable a QR redirection process to allow desktop users to continue an onboarding process from a mobile device, thereby increasing the quality of capturing process. However, an extra application backend logic is needed in order to enable this feature.

Next, required steps will be explained so integrators can add this logic to its backend.

Setting QR configuration parameters

When application backend makes an authentication request to authentication API (as explained in Architecture Overview integration section), it sends data configuration along with API_KEY. In addition to other required parameters (depending on customer needs), The stage with qr value and option.qr.redirectionUrl parameters must be sent, specifying the URL where QR users will be redirected.

For this explanation, following values will be configured.

    {
        "platform": "web",
        "operationMode": "idv",
        "flowSetup": {
            "core": {
                "confirmProcess": true
            },
            "stages": [
                "document",
                "qr"
            ],
            "options": {
                "document": {
                "captures": [
                    {
                    "documentTypes": [
                        "ES2_ID"
                    ]
                    }
                ]
                },
                "qr": {
                  "redirectionUrl": "https://{APPLICATION_BACKEND_SERVER}/auth/?user_id={USER_ID}"
                }
            }
        }
    }
  • APPLICATION_BACKEND_SERVER: URL of application backend server where web requests will be served. This can be the same as the one serving authentication requests or a different one.
  • USER_ID: Identifier of the user who has made the request to XpressID backend. This identifier needs to be created previously and will be related to the access token obtained through this call.

Once a valid access token is obtained, application backend logic must associate this access token to USER_ID so following requests (via QR redirection) made with query string ?user_id=USER_ID can be related to its corresponding access token. This process is freely managed by integrators but we strongly recommend to use any kind of database management.

Finally, application backend logic will use this access token to serve XpressID to frontend so users can start an onboarding as explained in Iframe integration section.

Adding a QR web server to backend

Once XpressID iframe is embedded into a web page, if it was configured with QR redirection as shown in previous step, it will initially show a QR asking user to continue process on a mobile or a desktop device. QR related URL will be: https://{APPLICATION_BACKEND_SERVER}/?user_id={USER_ID}.

When a user captures this QR it will be redirected to application backend server URL set before in options.qr.redirectionUrl parameter. Therefore, this request must also be handled by application backend logic. This logic will retrieve USER_ID parameter from the request and will get its related access token afterwards. Note that this access token is the one obtained in previous call to XpressID authentication API.

Finally, application backend logic will use this access token to serve XpressID iframe to frontend for second time. However, this time onboarding will be done from a mobile device so QR won't be shown and user can start an onboarding normally.

Note that XpressID will automatically distinguish between mobile and desktop devices so previously configured options.qr.redirectionUrl and stages withe qr value won't take any effect and will be ignored.

QR view Events on Desktop

The QR view on desktop can emit events to facilitate interaction between a mobile device and the desktop during the QR code capture and handling process. These events provide a way to monitor and manage the process from the desktop.

Event Details

Event Name Description Triggered By
QRProcessCaptured QR code captured on mobile device. Mobile device scanning the QR code.
QRClosedModel User closes the informative pop-up. User interaction (clicking "Close").
QRProcessOnboardingError Error encountered on the mobile device. Error during mobile onboarding process.
QRProcessCancelled User cancels the process on the mobile device. User clicking "X" on the mobile device.
QRProcessCompleted Process successfully completed on the mobile device. Mobile device completing the onboarding process.