Skip to content

Installation

There are two options to start a project:

  1. Using pre-built example projects that will fit into your solution framework.
  2. Following the instructions provided in section two.

1. List of projects examples by framework

Download the example that fits your needs:

  1. Vue
  2. React
  3. Angular
  4. Lit Element

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

  1. Download this zip file and then decompress it.

  2. Copy your own SDK files to the root of the demo folder.

  3. 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!              │
         │                                                   │
         └───────────────────────────────────────────────────┘
    
  4. Go to https://localhost:3000/demo, or the value which appeared in the - Local field 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 initializeSdk function. 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
    }
}
This optional configuration object can be passed while initializing the SDK. If not provided, the SDK will use its default settings.

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"
}
Setup configuration example

{
    "$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 the initializeSdk method to begin the capture process. See the initializeSdk method 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 the active flow, 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’s detail property 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")
            }
        }
    }
    }