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.
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. |