---
title: Integrating PingOne Recognize in JavaScript
description: Integrate PingOne Recognize biometric enrollment and authentication into JavaScript apps using the Orchestration SDK with PingOne Advanced Identity Cloud journeys
component: orchsdks
page_id: orchsdks:journey:use-cases/recognize/javascript/index
canonical_url: https://developer.pingidentity.com/orchsdks/journey/use-cases/recognize/javascript/index.html
llms_txt: https://developer.pingidentity.com/orchsdks/llms.txt
docs_for_agents: https://developer.pingidentity.com/build-with-ai/docs-for-agents.md
revdate: Mon, 14 Sep 2026 14:00:00 +0000
keywords: ["PingOne Recognize", "Biometric", "Face", "Recognition", "Journey", "Integration", "SDK", "JavaScript"]
section_ids:
  install: Step 1. Installing the module
  callback: Step 2. Handling the Recognize callback
  error_handling: Error handling
---

# Integrating PingOne Recognize in JavaScript

[icon: circle-check, set=far]PingOne Advanced Identity Cloud [icon: js, set=fab]JavaScript

The PingOne Recognize package abstracts away the underlying biometric enrollment and authentication protocol, so you can add strong, passwordless authentication to your web app with minimal code.

During a journey, the server returns a single callback, `PingOneRecognizeCallback`, and tells your app which ceremony to run using the `operationType` output field:

* `ENROLL`: perform a biometric enrollment ceremony.

* `AUTHENTICATE`: perform a biometric authentication ceremony.

The `@forgerock/recognize` package renders the ceremony as a web component in your page. The component handles the camera stream, guides the user through the ceremony, and creates the biometric template in the browser. Your app creates a PingOne Recognize client from the callback outputs, mounts the component, observes its events, and submits the outcome to the journey through the callback.

Your app never configures the underlying PingOne Recognize web SDK itself. All configuration, such as the service URL, the customer name, and the username, arrives in the callback output from the node.

Before you start, make sure your app meets the following requirements:

* The page is served over HTTPS, so the browser grants the ceremony access to the camera.

* The device has a camera the browser can access; the ceremony captures the user's face.

Also, ensure that you have completed the [server-side setup](https://docs.pingidentity.com/pingoneaic/latest/advanced-use-cases/pingone-recognize-service.html) for PingOne Recognize.

## Step 1. Installing the module

Add the `@forgerock/recognize` package to your Orchestration SDK project.

The package embeds the underlying PingOne Recognize web SDK, so no additional repository configuration is needed to install it.

Install the package with npm:

```shell
npm install @forgerock/recognize
```

The Recognize callback is delivered by the `@forgerock/journey-client` package, which your app already uses to run journeys.

## Step 2. Handling the Recognize callback

The server tells your app which ceremony to run through the callback's `operationType` output. It returns a `PingOneRecognizeCallback` with `operationType` = "ENROLL" to trigger an enrollment ceremony, and with `operationType` = "AUTHENTICATE" to request a biometric authentication ceremony.

Your app handles both ceremonies with the same sequence of calls: find the callback in the step's callback list, create a PingOne Recognize client from the callback outputs, mount the ceremony component into an element in your page, and set the results on the callback when the ceremony completes.

The following example code reads the `operationType` output and maps it to the ceremony type the client expects, so the same code runs either ceremony:

```typescript
import { callbackType, PingOneRecognizeCallback } from '@forgerock/journey-client';

import { recognize } from '@forgerock/recognize';
import type { RecognizeWebComponentConfiguration } from '@forgerock/recognize/types';


// Find the Recognize callback in the step's callback list
const recognizeCallback = step.callbacks.find(
  (cb) => cb.getType() === callbackType.PingOneRecognizeCallback,
) as PingOneRecognizeCallback; (1)

// Map the operation type the server set to the ceremony type the client expects
const ceremonyType = recognizeCallback.getOperationType() === 'ENROLL' ? 'enroll' : 'auth'; (2)

// Assemble the ceremony configuration from the callback outputs,
// including any extra options the journey node passes to the Recognize web SDKs
const config: RecognizeWebComponentConfiguration = {
  customer: recognizeCallback.getCustomerName(),

  serviceURL: recognizeCallback.getServiceURL(),

  ...recognizeCallback.getOptions(),
};

if (recognizeCallback.getTransactionData()) {
  config.transactionData = recognizeCallback.getTransactionData();
}

const client = recognize(config); (3)

// Subscribe to the ceremony events, then start the ceremony
await new Promise((resolve, reject) => {
  client.subscribe({ (4)
    next: (event) => {
      // Ceremony progress, such as step changes
    },

    error: (err) => {
      // The ceremony failed; submit the error detail to the journey
      recognizeCallback.setClientError(err.error.message);
      recognizeCallback.setClientErrorCode(String(err.error.code));
      resolve();
    },

    complete: (data) => {
      // The ceremony succeeded; submit the signed JWT and Recognize ID
      if (data.jwt) {
        recognizeCallback.setSignedJwt(data.jwt);
        recognizeCallback.setRecognizeId(JSON.parse(atob(data.jwt.split('.')[1])).sub);
      }
      resolve();
    },
  });

  client
    .init({ (5)
      mode: 'mount',
      container: document.getElementById('recognize'),
      type: ceremonyType,
      username: recognizeCallback.getUsername(),
    })
    .then((initError) => {
      if (initError) {
        recognizeCallback.setClientError(initError.error.message);
        recognizeCallback.setClientErrorCode(String(initError.error.code));
      }
      resolve();
    });
});

// Remove the ceremony component
client.dispose(); (6)

// Continue the journey
step = await journeyClient.next(step); (7)
```

1 The `PingOneRecognizeCallback` arrives in the step's callback list like any other callback. The code above finds it by its callback type, and casts it to the `PingOneRecognizeCallback` class from the `@forgerock/journey-client` package, which provides the Recognize methods the snippet calls.

2 The server tells your app which ceremony to run through the callback's `operationType` output: "ENROLL" for enrollment, "AUTHENTICATE" for authentication. Read it with `getOperationType()`, and map the value to the ceremony type the recognize client expects: `enroll` for enrollment, `auth` for authentication.

3 Create the Recognize client with `recognize()`, passing it the ceremony configuration assembled from the callback outputs:

* `customer`: the customer name registered on the Recognize server, read from the callback with `getCustomerName()`.

* `serviceURL`: the authentication service URL of the Recognize server, read from the callback with `getServiceURL()`.

* the transaction data, when the node configured it, read from the callback with `getTransactionData()`.

* any extra options the journey node passes to the Recognize web SDKs, read from the callback with `getOptions()`.

4 Subscribe an observer to the client with `subscribe()` before starting the ceremony. The observer receives the ceremony outcome:

* `complete`: the ceremony succeeded. Set the values the server needs to verify the ceremony result on the callback:

  * The signed JWT from the data, with `setSignedJwt()`. The Keyless authentication service issued it after the user's captured face matched their enrolled biometric template and passed the liveness checks, attesting that the ceremony completed on the device. The Keyless authentication service, not your app, signs the JWT, so the server can verify its signature rather than trust a result claimed by the client. If the node configured transaction data, the signed JWT also carries it, which binds the ceremony to that specific transaction.

  * the user's PingOne Recognize identifier, with `setRecognizeId()`, read from the `sub` claim of the signed JWT.

* `error`: the ceremony failed. Set the error message and code on the callback with `setClientError()` and `setClientErrorCode()` so the journey can continue on its Client Error outcome.

5 Start the ceremony with `init()`, passing it the element in your page to mount the ceremony component in, the ceremony type you derived in the previous step, and the username read from the callback with `getUsername()`. The component handles the camera stream and guides the user through capturing their face.

6 When the ceremony finishes, remove the component with `dispose()`.

7 Continue the journey by passing the step to `journeyClient.next()`. The callback submits the values you set on it to the server, and the journey continues on the outcome matching the ceremony result.

## Error handling

The recognize client reports every failure through the observer's error callback as a `RecognizeError` object:

```typescript
{
  error: {
    code: 2002,                           // Numeric code from the Recognize error code set
    message: 'CAMERA_PERMISSION_DENIED',  // Name of the error in the code set
    cause: originalError,                 // The underlying error, when present
  },
}
```

Call the callback's `setClientError()` and `setClientErrorCode()` methods with the error's `message` and `code`, so your server receives the error detail regardless of how your app handles the failure.

If the ceremony cannot start, `init()` resolves with a `RecognizeError` instead of reporting it through the observer, for example when the browser cannot load the underlying WebAssembly module.

For example, to handle the errors users most commonly encounter during a ceremony:

```javascript
switch (err.error.code) {
  case 1005: // User cancelled
    showRetryPrompt('Face scan cancelled');
    break;

  case 3004: // Face not matching
    showRetryPrompt('Face did not match');
    break;

  case 1007: // No network connection
    showRetryPrompt('No network connection');
    break;

  case 3007: // User lockout
    showLockoutScreen();
    break;

  case 2002: // Camera permission denied
    requestCameraPermission();
    break;

  default:
    logUnexpectedError(err);
}

// Continue the journey to report client errors back to the server
step = await journeyClient.next(step);
```

The error codes group by source: integration errors in the 1000 range, such as 1005 when the user cancels the ceremony, camera errors in the 2000 range, and errors about the user's enrollment and biometric match in the 3000 range, such as 3003 when the user is not yet enrolled.

Liveness errors use the 4000 range, Recognize server errors the 5000 range, and device security errors the 6000 range.

The full set of codes is available as the `RECOGNIZE_ERROR_CODE` constant in the `@forgerock/recognize` package.
