Installation¶
There are two options to start a project:
- Using pre-built example projects that will fit into your solution framework.
- Following the instructions provided in section two.
1. List of projects examples by framework¶
Download the example that fits your needs:
2. Manual integration¶
We provide a demo application that shows how to integrate and run the SDK. In this section, it is explained how to run the demo as well as how to use it and what contains.
Steps to launch Demo¶
-
Download this zip file and then decompress it.
-
Copy your own SDK files to the root of the demo folder.
-
To run the demo, open a terminal in the extracted folder and run the following commands:
npm i -g serve serve . --ssl-key=certs/key.pem --ssl-cert=certs/cert.pem ┌───────────────────────────────────────────────────┐ │ │ │ Serving! │ │ │ │ - Local: https://localhost:3000 │ │ - On Your Network: https://192.168.9.192:3000 │ │ │ │ Copied local address to clipboard! │ │ │ └───────────────────────────────────────────────────┘ -
Go to
https://localhost:3000/demo, or the value which appeared in the- Localfield above.
Files¶
Inside the demo folder, you'll find the following key files and directories:
index.html– The main HTML entry point for launching the demo.setup.js– Integration script responsible for loading and initializing the SDK.config.json– Contains the full configuration for the SDK (styles, setup options, etc.).assets/– Directory containing the SDK bundle and all necessary dependencies.certs/– Includes a self-signed public key and private certificate used to enable SSL for local demo execution.(Refer to the next section: Creating Self-Signed Certificate).
These files are essential for the demo to run correctly. Modify them with care to avoid misconfiguration or breaking the setup.
Creating Self-Signed Certificate¶
To launch the demo without SSL warning you can create a self-signed certificate with the following command:
# openssl self-signed certificate
openssl req -x509 \
-sha256 -days 356 \
-nodes \
-newkey rsa:2048 \
-subj "/CN=docs.veridas.com/C=ES/L=Pamplona" \
-keyout rootCA.key -out rootCA.crt
Defining the SDK Container in index.html¶
The index.html file loads the SDK package, while the setup.js script handles the loading and initiation of the SDK. It also creates a container where the SDK will be displayed.
This container must have a defined width and height for the sdk to be rendered correctly.
We recommend in mobile devices the size gets almost the full size of the viewport in order to avoid content overflows. This could be done using the following CSS rule "width: 100dvw, height:100dvh"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<link rel="icon" type="image/x-icon" href="assets/images/veridasLogo.png" />
<title>SDK Video</title>
<script type="module" defer src="assets/vd-video.umd.js"></script>
<script src="setup.js"></script>
</head>
<body>
<div id="demoContainer" style="width:100dvw; height: 100dvh"></div>
</body>
</html>
You can also load the SDK directly, adding the
<vd-video>tag inside the ‘demoContainer’ container.
Configuration input¶
When initializing the SDK, you can configure and customize the SDK. That way, you can provide a more personalized approach.
You can set its configuration, with the initializeSdk function. This function supports two types of configuration:
- Config by File: You can set the configuration, passing the path to your configuration file (config.json), exposed on your server. For example:
VDVideo.initializeSdk("./config.json")
- Config by Object: You can also set the configuration, passing an config object to the
initializeSdkfunction. For example:
const configObject = {
...
setup: {
documents: "ES2_ID" ,
},
...
}
VDVideo.initializeSdk(configObject)
This config object needs to follow the correct schema. Each field should have the expected data type and structure (e.g., strings, booleans, nested objects). Providing a misconfigured will cause the SDK to fail during initialization.
You can get more details about the configuration when starting the SDK, go to the Initialize section of this method documentation.
Also, one of the important properties to set up is the pathAssets. This is used to indicate the location to the assets folder and it is explained in the following section.
Property setup.pathAssets¶
The setup.pathAssets property is essential for the SDK to correctly load its internal assets. You can find its default value in the SDK Video Configuration table.
There are two ways to define the pathAssets property:
- Absolute path: Provide the complete URL or directory path to the assets folder.
- Relative path: Specify the path relative to the SDK’s location.
If your assets folder is located outside the SDK directory, you can use ../ to traverse up the directory tree from the SDK location, or use an absolute path to point directly to the folder.
Examples of configurations¶
Here is an example of a configuration object with optional configuration parameters:
Object¶
const optionalConfigObject = {
setup: {
closeButtonShow: true ,
detectionTimeout: 60000,
documents: "ES_IDCard_2021"
language: "ES",
logoShow: true,
instructionsShow: true,
reviewShow: true,
nameSurnameShow: true
}
}
Configuration via JSON File¶
You can configure the SDK using a JSON file, and there are two supported approaches depending on your project structure and preferences:
1. Inline Configuration¶
In this approach, all configuration values are written directly inside a single JSON file:
{
"$schema": "./assets/schemas/video-sdk-schema.json",
"styles": {},
"texts": {},
"medias": {},
"setup": {
"closeButtonShow": true,
"detectionTimeout": 60000,
"documents": "ES_IDCard_2021",
"language": "ES",
"logoShow": true,
"instructionsShow": true,
"reviewShow": true,
"nameSurnameShow": true
}
}
This format is useful when you want everything in one place and prefer not to manage multiple files.
2. External References Configuration¶
This approach allows you to keep styles, texts, media, and setup definitions in separate external JSON files. This is helpful for organizing large configurations or reusing settings across different environments:
{
"$schema": "./assets/schemas/video-sdk-schema.json",
"styles": "./styles.json",
"texts": "./texts.json",
"medias": "./media.json",
"setup": "./setup.json"
}
{
"$schema": "./assets/schemas/setup-sub-schema.json",
"closeButtonShow": true,
"detectionTimeout": 60000,
"documents": "ES_IDCard_2021",
"language": "ES",
"logoShow": true,
"instructionsShow": true,
"reviewShow": true,
"nameSurnameShow": true
}
Each referenced file (styles.json, texts.json, etc.) must follow its corresponding schema and structure.
Moreover, it's not mandatory to provide all the configuration parameters suggested in the above examples. You can selectively pass only certain configurations. For any parameters not explicitly specified, the SDK will seamlessly utilize the corresponding default settings.
Typography Load¶
To correctly render the default User Interface (UI) theme provided by Veridas, the SDK automatically loads two fonts: --genuine-custom-font-family-primary and --genuine-custom-font-family-secondary. These are applied only if no custom fonts are specified in the familyPrimary and familySecondary fields within the styles configuration object.
If you choose to override the default fonts by specifying your own, make sure those fonts are properly loaded, especially if they are not natively supported by the browser.
The following example demonstrates how to load the PublicSansNormal font, using both a local file and a URL:
@font-face {
font-family: PublicSansNormal;
src: url(./assets/fonts/PublicSansNormal.ttf);
}
@font-face {
font-family: PublicSansNormal;
src: url(https://fonts.googleapis.com/css2?family=PublicSansNormal:ital,wght@1,500&display=swap);
}
@font-face {
font-family: PublicSansRegular;
src: url(./assets/fonts/PublicSansRegular.ttf);
}
@font-face {
font-family: PublicSansRegular;
src: url(https://fonts.googleapis.com/css2?family=PublicSansRegular:ital,wght@1,500&display=swap);
}
Then, in your SDK configuration, you can specify the fonts like this:
{
"styles": {
"theme": {
"font": {
"familyPrimary": "PublicSansNormal",
"familySecondary": "PublicSansRegular"
}
}
}
}
Note: If the specified fonts fail to load, the SDK will fall back to the system default, typically Arial.
SDK Loader¶
In the demo project, the SDK does not start automatically when the page loads. Instead, once the window.onload event is triggered, the SDK’s custom tag <vd-video> is already present in the DOM, and the demo initializes the event listeners associated with the SDK.
What actually happens on window.onload is that all the available SDK events are accessed through window.vdVideo.VDVideo.events. These can then be subscribed to as needed.
let SdkEvents;
window.onload = initializeElementsValue;
function initializeElementsValue() {
SdkEvents = window.vdVideo.VDVideo.events;
addListeners();
}
This function does not start the SDK, it only prepares the environment by exposing the SDK events, allowing the application to attach custom listeners.
Event Listeners¶
The SDK emits a series of custom events that you can listen to in order to execute specific logic during the capture flow. These events give you fine-grained control over the SDK lifecycle. You can find the full list and structure of events in the API section.
Below are three key events that are especially important for integrating and managing the SDK properly:
VD_mounted: This event is fired once the SDK has been loaded and is ready to be initialized. At this point, the SDK is fully available in memory, and you can safely call theinitializeSdkmethod to begin the capture process. See theinitializeSdkmethod for more details on how to start the SDK.
VD_restartProcess: If the detection process fails and needs to be restarted (e.g., due to user movement or environmental issues), this event is triggered. In the case of theactiveflow, a new random challenge must be generated and passed again.
VD_capture: This event is emitted when the detection process completes successfully. The captured data is included in the event’sdetailproperty and can be processed or stored according to your requirements.
Here's an example showing how to listen for these events and initialize the SDK. You can place this code in your main setup file:
/**
* @name eventHandlers
* @description Object with the event handlers for the SDK events
* @type {Object}
*/
const eventHandlers = {
VD_mounted: onSDKMounted,
VD_restartProcess: handleRestartProcess,
VD_capture: onSDKResult
}
/**
* @name handleEvent
* @description Handles the SDK events for the events in the eventHandlers object
* @param {Object} e Event object
* @returns {void}
*/
function handleEvent(e) {
const handler = eventHandlers[e.type]
if (handler) {
handler(e)
}
}
/**
* @name addListeners
* @description Adds the event listeners for the SDK events in the `SdkEvents` array
*/
function addListeners() {
SdkEvents.forEach(sdkEvent => {
addEventListener(sdkEvent, e => {
handleEvent(e)
})
})
}
Initialize¶
Once the VDAlive_mounted SDK event is dispatched, you can invoke the function responsible for launching the SDK. This function accepts an optional configuration, which can be provided either through a file or an inline object.
This is illustrated in the example below.
async function launchSDK() {
VDVideo = document.querySelector("vd-video")
if (isConfigByFile) {
await VDVideo.initializeSdk('./config.json')
} else if (configObject) {
await VDVideo.initializeSdk(configObject)
} else {
await VDVideo.initializeSdk()
}
}
In the example above, the SDK can be initialized using a configuration, either from a file or a JavaScript object. If no configuration is provided, the SDK will fall back to its default settings.
Output Data from VD_capture Event¶
Finally, when the detection process ends correctly, the SDK emits VD_capture event with the output data in its detail.
The output files are:
- video: mp4 video with recording of the full flow.
Basic setup.js file¶
This is how we suggest your setup.js file should look like at the end:
let VDVideo, SdkEvents, isConfigByFile, demoContainer
window.onload = () => loadSDK()
function loadSDK() {
SdkEvents = window.vdVideo.VDVideo.events
demoContainer = document.querySelector('#demoContainer')
demoContainer.innerHTML = "<vd-video></vd-video>"
addListeners()
}
/**
* @name eventHandlers
* @description Object with the event handlers for the SDK events
* @type {Object}
*/
const eventHandlers = {
VD_mounted: onSDKMounted,
VD_restartProcess: handleRestartProcess,
VD_capture: onSDKResult,
VD_cameraVideoPlayStarted: onCameraVideoPlayStarted,
VD_processFinished: onProcessFinished,
VD_closeButtonClicked: onCloseButtonClicked,
VD_detectionTimeout: onDetectionTimeout
}
/**
* @name handleEvent
* @description Handles the SDK events for the events in the eventHandlers object
* @param {Object} e Event object
* @returns {void}
*/
function handleEvent(e) {
const handler = eventHandlers[e.type]
if (handler) {
handler(e)
}
}
/**
* @name addListeners
* @description Adds the event listeners for the SDK events in the `SdkEvents` array
*/
function addListeners() {
SdkEvents.forEach(sdkEvent => {
addEventListener(sdkEvent, e => {
handleEvent(e)
})
})
}
async function launchSDK() {
VDVideo = document.querySelector("vd-video")
if (isConfigByFile) {
await VDVideo.initializeSdk('./config.json')
} else if (configObject) {
await VDVideo.initializeSdk(configObject)
} else {
await VDVideo.initializeSdk()
}
}
3. Webviews Integration¶
The SDK is compatible with Meta WebViews, i.e. you can run it in Facebook and Instagram apps for Android and iOS. We also support native WebViews for iOS and Android. However, we do not guarantee full compatibility or functionality with WebViews from applications built using other frameworks, including hybrid frameworks, the performance and behaviour may vary.
The following sections provide examples of how to implement these integrations for each platform.
To integrate SDK 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 SDK 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 SDK 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://sdk-url.com")
}
}
}
}