---
title: Enrollment
description: "\"Shows how to enroll a new user by connecting their facial biometrics to PingOne Recognize account.\""
component: recognize
page_id: recognize:web-sdk:web-sdk-guide-enrollment
canonical_url: https://docs.pingidentity.com/recognize/web-sdk/web-sdk-guide-enrollment.html
llms_txt: https://docs.pingidentity.com/recognize/llms.txt
docs_for_agents: https://developer.pingidentity.com/build-with-ai/docs-for-agents.md
section_ids:
  before-you-begin: Before you begin
  headless-integration: Headless integration
  component-integration: Web component integration
---

# Enrollment

Enrollment is the process of registering a new user by connecting their facial biometrics to a PingOne Recognize account. During this process, a full and unobstructed view of the user's face is required.

Enrollment with Web SDK can happen in two ways:

* Live enrollment using the PingOne Recognize Web SDK JavaScript libraries.

* [IDV Bridge SaaS](../idv-bridge/idv-bridge-saas.html) using the PingOne Recognize Authentication Service

This page explains how to perform interactive live enrollment on the front-end.

PingOne Recognize also supports [component interoperability](#idv-bridge:idv-bridge-component-interoperability), so that users can be enrolled using IDV Bridge (on-premises) or the Mobile SDK. Authentication can be performed later using JavaScript and the Web SDK.

## Before you begin

Review the [prerequisites requirements](web-sdk-prerequisites.html) before continuing.

## Headless integration

The `@keyless/sdk-web` library lets you integrate the PingOne Recognize Web SDK without using a user interface. Here's a simple enrollment:

> **Collapse: Details**
>
> ```javascript
> import {
>   addKeylessEventListeners,
>   createKeylessEnroll,
>   createKeylessMediaStream,
>   getKeylessCameraPermissionState,
>   getKeylessVideoMediaDevices,
>   getLastKeylessServerFrameTriggeredBiometricFilters,
>   getLastKeylessVideoFrameQuality,
>   importKeylessWebAssemblyModuleOrThrow,
>   isKeylessVideoMediaStreamAvailable,
>   KeylessError,
>   openKeylessWebSocketConnection,
>   reduceKeylessBiometricFiltersToTriggered,
>   removeKeylessEventListeners
> } from '@keyless/sdk-web'
>
> function requestTransactionJwtVerification(jwt) {}
> function requestUserCameraPermission() {}
>
> function handleCameraOperativityError(error) {}
> function handleImportKeylessWebAssemblyModuleError(error) {}
> function handleCreateKeylessMediaStreamError(error) {}
> function handleOpenKeylessWebSocketConnectionError(error) {}
>
> /**
>  * This event is fired when an error occurs during the enrollment process.
>  * The error object contains a `message` property that indicates the type of error.
>  */
> function onKeylessError(sym, error) {
>   /**
>    * Removing event listeners is advised on terminal events since
>    * no more than one attempt is allowed per enrollment symbol.
>    */
>   removeKeylessEventListeners(sym)
>
>   // will log the error code
>   console.error(error.message)
> }
>
> /**
>  * This event is fired when the enrollment process is complete.
>  * It does not fire for failed attempts, only successful ones.
>  */
> function onKeylessFinished(sym, message) {
>   /**
>    * Removing event listeners is advised on terminal events since
>    * no more than one attempt is allowed per enrollment symbol.
>    */
>   removeKeylessEventListeners(sym)
>
>   /**
>    * The `transactionJwt` is a JSON Web Token (JWT) that contains information
>    * about the enrollment transaction.
>    *
>    * This token is signed by the Keyless Authentication Service and can be used
>    * to verify the authenticity of the transaction.
>    *
>    * This operation is strictly backend-to-backend and should never be performed
>    * in client-side code.
>    */
>   requestTransactionJwtVerification(message.transactionJwt)
> }
>
> /**
>  * This event is useful for providing real-time feedback to users during
>  * the enrollment process, such as prompting them to adjust their position
>  * or lighting conditions to improve biometric recognition.
>  *
>  * The difference with "onKeylessVideoFrameQuality" is that this is from
>  * filters running on the server.
>  */
> function onKeylessFrameResults(sym, message) {
>   let filters
>
>   /**
>    * Returns an array of biometric filters that were triggered in the last frame.
>    * If no biometric filters were triggered, an empty array is returned.
>    */
>   filters = reduceKeylessBiometricFiltersToTriggered(message.filters)
>
>   /**
>    * Optionally, this function can be used to retrieve the filters that were triggered
>    * in the last frame.
>    *
>    * This can be useful if you need to access the last frame's triggered filters outside
>    * of the frame results event.
>    *
>    * If this function is used then this event is useful for requesting an update to the UI.
>    */
>   filters = getLastKeylessServerFrameTriggeredBiometricFilters(sym)
> }
>
> /**
>  * This event is useful for providing real-time feedback to users during
>  * the enrollment process, such as prompting them to adjust their position
>  * or lighting conditions to improve biometric recognition.
>  *
>  * The difference with "onKeylessFrameResults" is that this is from filters
>  * running on the client.
>  */
> function onKeylessVideoFrameQuality(sym, event) {
>   /**
>    * Will log an array of filters that were triggered in this frame.
>    * If no biometric filters were triggered, an empty array is returned.
>    */
>   console.log(event.filters)
>
>   /**
>    * Optionally, this function can be used to retrieve the quality of the last
>    * video frame.
>    *
>    * This can be useful if you need to access the last video frame quality outside
>    * of the video frame quality event.
>    *
>    * If this function is used then this event is useful for requesting an update to the UI.
>    */
>   console.log(getLastKeylessVideoFrameQuality(sym))
> }
>
> async function ensureCameraOperativity() {
>   let devices, state
>
>   devices = await getKeylessVideoMediaDevices()
>
>   /**
>    * If the error is MEDIA_DEVICES_NO_VIDEO_INPUTS, it means that
>    * the user's device doesn't have a camera.
>    */
>   if (devices instanceof Error && devices.message === KeylessError.MEDIA_DEVICES_NO_VIDEO_INPUTS) throw devices
>
>   state = await getKeylessCameraPermissionState()
>
>   /**
>    * If the camera permission state is not 'granted', request
>    * the user to grant camera access.
>    */
>   if (state !== 'granted') {
>     /**
>      * Ideally this function should take the user to a UI prompt
>      * where they can grant camera access to the website.
>      *
>      * The easiest way to trigger the browser's camera permission prompt
>      * is to call isKeylessVideoMediaStreamAvailable(), which will return
>      * a boolean indicating whether the user granted camera access or not.
>      */
>     requestUserCameraPermission()
>     throw new Error('camera permission state is not granted')
>   }
>
>   devices = await getKeylessVideoMediaDevices()
>
>   /**
>    * If the error is MEDIA_DEVICES_EMPTY_VIDEO_INPUT_LABEL, it means that
>    * even though the user has granted camera access, the browser requires
>    * the user to start a video stream to be able to read the camera labels.
>    *
>    * In this case, we perform a throwaway getUserMedia() request with
>    * isKeylessVideoMediaStreamAvailable() to start a video stream
>    * to be able to read the camera labels.
>    */
>   if (devices instanceof Error && devices.message === KeylessError.MEDIA_DEVICES_EMPTY_VIDEO_INPUT_LABEL) {
>     let available
>
>     available = await isKeylessVideoMediaStreamAvailable()
>     if (!available) throw new Error('video media stream is not available')
>
>     devices = await getKeylessVideoMediaDevices()
>   }
>
>   /**
>    * If we still have an error, throw an error to indicate that
>    * the media devices still could not be read correctly.
>    */
>   if (devices instanceof Error) throw devices
> }
>
> async function enrollWithKeyless() {
>   let enroll, options, stream, open
>
>   /**
>    * Create a Keyless enrollment symbol.
>    *
>    * This symbol must be kept in memory for the duration of the enrollment process.
>    * To perform multiple enrollments, a new symbol must be created for each enrollment.
>    */
>   enroll = createKeylessEnroll()
>
>   /**
>    * Add event listeners through the Keyless enrollment symbol.
>    * These listeners will handle events during the enrollment process.
>    */
>   addKeylessEventListeners(enroll, [
>     { name: 'error', callback: (error) => onKeylessError(enroll, error) },
>     { name: 'finished', callback: (message) => onKeylessFinished(enroll, message) },
>     { name: 'frame-results', callback: (message) => onKeylessFrameResults(enroll, message) },
>     { name: 'video-frame-quality', callack: (event) => onKeylessVideoFrameQuality(enroll, event) }
>   ])
>
>   options = {
>     authorization: {
>         token: 'USER_AUTHORIZATION_FROM_CUSTOMER'
>     },
>     customer: { name: 'CUSTOMER_NAME' },
>     key: { id: 'IMAGE_ENCRYPTION_KEY_ID', value: 'IMAGE_ENCRYPTION_PUBLIC_KEY' },
>     transaction: {
>       data: 'DATA_FROM_CUSTOMER_SERVER_TO_BE_SIGNED'
>     },
>     username: 'USERNAME',
>     ws: { url: 'KEYLESS_AUTHENTICATION_SERVICE_URL' }
>   }
>
>   /**
>    * Create a media stream from the user's video input media device.
>    * This stream will be used to capture video frames for biometric analysis.
>    *
>    * Note: The user must grant permission to access the media device.
>    */
>   stream = await createKeylessMediaStream()
>   if (stream instanceof Error) return handleCreateKeylessMediaStreamError(stream)
>
>   /**
>    * Open a WebSocket connection to the Keyless Authentication Service.
>    * This connection will be used to process video frames and receive enrollment results.
>    */
>   open = await openKeylessWebSocketConnection(enroll, options)
>   if (open instanceof Error) return handleOpenKeylessWebSocketConnectionError(open)
> }
>
> importKeylessWebAssemblyModuleOrThrow()
>   .then(() =>
>     ensureCameraOperativity()
>       .then(() => enrollWithKeyless())
>       .catch(handleCameraOperativityError)
>   )
>   .catch(handleImportKeylessWebAssemblyModuleError)
> ```

## Web component integration

This section shows how to integrate the PingOne Recognize Web SDK directly into a web application.

* React

* Vue

* Embedded

```javascript
import '@keyless/sdk-web-components'

export function KeylessEnroll() {
  function requestTransactionJwtVerification(jwt) {}

  onError = (event) => {
    // will log the error code
    console.log(event.message)
  }

  onFinished = (event) => {
    /**
     * The `transactionJwt` is a JSON Web Token (JWT) that contains information
     * about the enrollment transaction.
     *
     * This token is signed by the Keyless Authentication Service and can be used
     * to verify the authenticity of the transaction.
     *
     * This operation is strictly backend-to-backend and should never be performed
     * in client-side code.
     */
    requestTransactionJwtVerification(message.transactionJwt)
  }

  return (
    <kl-enroll
      authorization-token='USER_AUTHORIZATION_FROM_CUSTOMER'
      customer='CUSTOMER_NAME'
      enable-camera-instructions
      key-id='IMAGE_ENCRYPTION_KEY_ID'
      lang='en'
      onerror={onError}
      onfinished={onfinished}
      public-key='IMAGE_ENCRYPTION_PUBLIC_KEY'
      size='375'
      theme='light'
      transaction-data='DATA_FROM_CUSTOMER_SERVER_TO_BE_SIGNED'
      username='USERNAME'
      ws-url='KEYLESS_AUTHENTICATION_SERVICE_URL'
    />
  )
}
```

```javascript
<script setup>
import '@keyless/sdk-web-components'

function requestTransactionJwtVerification(jwt) {}

function onError(event) {
// will log the error code
console.log(event.message)
}

function onFinished(event) {
/**
* The `transactionJwt` is a JSON Web Token (JWT) that contains information
* about the enrollment transaction.
*
* This token is signed by the Keyless Authentication Service and can be used
* to verify the authenticity of the transaction.
*
* This operation is strictly backend-to-backend and should never be performed
* in client-side code.
*/
requestTransactionJwtVerification(message.transactionJwt)
}
</script>

<template>
<kl-enroll
customer="CUSTOMER_NAME"
enable-camera-instructions
@error="onError"
@finished="onFinished"
key="IMAGE_ENCRYPTION_PUBLIC_KEY"
key-id="IMAGE_ENCRYPTION_KEY_ID"
lang="en"
size="375"
theme="light"
transaction-data='DATA_FROM_CUSTOMER_SERVER_TO_BE_SIGNED'
username="USERNAME"
ws-url="KEYLESS_AUTHENTICATION_SERVICE_URL"
/>
</template>
```

```javascript
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Enroll</title>
    <style>
      * {
        box-sizing: border-box;
      }

      body {
        align-items: center;
        display: flex;
        justify-content: center;
        margin: 0;
        min-height: 100vh;
        padding: 8px;
      }

      kl-enroll {
        border: 1px solid lightgray;
      }
    </style>
  </head>
  <body>
    <kl-enroll
      customer="CUSTOMER_NAME"
      enable-camera-instructions
      key="IMAGE_ENCRYPTION_PUBLIC_KEY"
      key-id="IMAGE_ENCRYPTION_KEY_ID"
      lang="en"
      size="375"
      theme="light"
      transaction-data='DATA_FROM_CUSTOMER_SERVER_TO_BE_SIGNED'
      username="USERNAME"
      ws-url="KEYLESS_AUTHENTICATION_SERVICE_URL"
    ></kl-enroll>
    <script src="./node_modules/@keyless/sdk-web-components/index.js" type="module"></script>
    <script>
      const enroll = document.querySelector('kl-enroll')

      function requestTransactionJwtVerification(jwt)

      enroll.addEventListener('error', (event) => {
        // will log the error code
        console.log(event.message)
      })

      enroll.addEventListener('finished', (event) => {
        /**
         * The `transactionJwt` is a JSON Web Token (JWT) that contains information
         * about the enrollment transaction.
         *
         * This token is signed by the Keyless Authentication Service and can be used
         * to verify the authenticity of the transaction.
         *
         * This operation is strictly backend-to-backend and should never be performed
         * in client-side code.
         */
        requestTransactionJwtVerification(message.transactionJwt)
      })
    </script>
  </body>
</html>
```
