---
title: Launching a flow with the widget
description: Launch a prepared flow with a widget.
component: davinci
page_id: davinci:integrating_flows_into_applications:davinci_launching_a_flow_with_the_widget
canonical_url: http://docs.pingidentity.com/davinci/integrating_flows_into_applications/davinci_launching_a_flow_with_the_widget.html
revdate: May 6, 2024
section_ids:
  configuring-the-flow: Configuring the Flow
  about-this-task: About this task
  steps: Steps
  creating-an-application: Creating an Application
  about-this-task-2: About this task
  steps-2: Steps
  adding-cors-settings-in-pingone: Adding CORS Settings in PingOne
  steps-3: Steps
  integration-example: Integration example
  html-invocation-code: HTML invocation code
  server-side-javascript-code: Server-side JavaScript code
  getting-the-sdk-token: Getting the SDK token
  using-the-widget-with-a-return-url: Using the widget with a return URL
  limiting-the-cookies-passed-by-the-widget: Limiting the cookies passed by the widget
---

# Launching a flow with the widget

Launch a prepared flow with a widget.

This method is effective when you want to launch the flow within the current page instead of redirecting the user.

|   |                                                                                                                                                                                                                                                                                                                 |
| - | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|   | * You can't launch the DaVinci widget in an iframe.

* You can find information on switching between using a flow for a PingOne redirect integration and an integration using the DaVinci widget in [Switching between PingOne and DaVinci widget integrations](davinci_switch_between_flow_integrations.html). |

## Configuring the Flow

Prepare a flow to be launched with the widget.

### About this task

|   |                                                                                                                                                                                                                                                                                                                                                                                                                 |
| - | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|   | * This procedure only covers the steps and nodes required to prepare a flow for widget invocation. It assumes that you have already created a flow for the purpose you have in mind. See the [Getting started with DaVinci](../flows/davinci_getting_started.html) and [DaVinci Best Practices](../davinci_best_practices/davinci_best_practices.html) documentation for more information about building flows. |

### Steps

1. Click the **Flows** tab.

2. Find the flow and click **... > Edit**

3. Add a nonce to the input schema.

   |   |                                                                                                                                                |
   | - | ---------------------------------------------------------------------------------------------------------------------------------------------- |
   |   | When you launch the flow, you provide a nonce value. This value is returned when the flow completes, letting you verify the flow's completion. |

   1. Click **Flow Options ( [icon: ellipsis-v, set=fa]) > Input Schema**.

   2. Click **Add**.

   3. In the **Parameter Name** field, enter `nonce`.

   4. In the **Data Type** list, select **String**.

   5. Select **Required**.

   6. Click **Save**.

4. At the end of the success path, add a PingOne Authentication node or an HTTP node to send a success response.

5. At the end of any failure paths, add a PingOne Authentication node or an HTTP node to send an error response.

6. Click **Save**.

7. Click **Deploy**.

## Creating an Application

Create an application in DaVinci to enable your flow.

### About this task

|   |                                                                                         |
| - | --------------------------------------------------------------------------------------- |
|   | If you want to use an existing application to launch the flow, you can start at step 5. |

### Steps

1. Click the **Applications** tab.

2. Click **Add Application**.

   The **Add Application** modal opens.

3. In the **Name** field, enter a name for the application.

4. Click **Create**.

5. Find the application and click **Edit**.

6. On the **General** tab, note the following parameters:

   1. Note the **Company ID**.

7. Create a flow policy:

   1. Click the **Flow Policy** tab.

   2. Click **[icon: plus, set=fa]Add Flow Policy**.

   3. In the **Name** field, enter a name for the flow policy.

   4. In the flow list, select your flow.

   5. In the version list, select **Latest Version**.

   6. Click **Create Flow Policy**.

      The **Edit Your Weight Distribution** modal opens.

      |   |                                                                                                                                                            |
      | - | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
      |   | This example only uses one flow, but if your flow policy included multiple flows or flow versions, you could use this modal to split traffic between them. |

   7. Click **Save Flow Policy**.

   8. Note the **Policy ID** of your flow policy.

## Adding CORS Settings in PingOne

Add the domain from which the flow will be launched to the DaVinci application in PingOne to prevent CORS issues.

### Steps

1. Sign on to PingOne and go to **Applications** > **Applications**.

2. Click the **PingOne DaVinci Connection** application.

   You can also add the CORS setting to any other PingOne application. For tracking and consistency, we recommend adding the CORS setting to the PingOne DaVinci application.

3. Click the **Configuration** tab.

4. Click the **Pencil** icon.

5. In the **CORS Settings** section, select **Allow specific origins**.

6. In the **Allowed Origins** field, enter the domain from which you plan to launch the flow.

7. Click **Save**.

## Integration example

This example is a simple hello-world application that launches a flow using the widget. The HTML code specifies the flow policy ID. A server-side JavaScript process securely provides the API key and company ID. The example checks for an existing user session, which is stored in a cookie created by the Return Success node during a previous flow execution.

|   |                                                                                                                                                                                                                                                                                                    |
| - | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|   | The following example won't work unless you add your region-specific information. Replace any instances of `<region>` with your regional top-level domain:- Use `.com` for North America.

- Use `.ca` for Canada.

- Use `.eu` for EMEA.

- Use `.asia` for APAC.

- Use `.com.au` for Australia. |

|   |                                                                                                                                                                                                         |
| - | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|   | The following example uses the `davinci.js` widget file, which uses default DaVinci CSS rules. To invoke the flow without using the default DaVinci CSS rules, use the `davinci-no-css.js` widget file. |

### HTML invocation code

```html
<!DOCTYPE html>
<html lang="en">
<meta charset="UTF-8" />

<head>
    <title>Simple HTML/JS widget Testing</title>
    <link rel="stylesheet" href="https://assets.pingone.<region>/ux/end-user-nano/0.1.0-alpha.9/end-user-nano.css" />
    <link rel="stylesheet" href="https://assets.pingone.<region>/ux/astro-nano/0.1.0-alpha.11/icons.css" />
</head>

<body>
    <h3>Simple HTML/JS widget Test</h3>
    <br />
    <p>Widget will be displayed below</p>
    <br />
    <div id="widget" class="dvWidget"></div>
    <script type="text/javascript" src="https://assets.pingone.<region>/davinci/latest/davinci.js"></script>
    <script>
        // Only the policyId is needed on the frontend.
        // API keys and companyId are securely managed server-side in bxi-server.js.
        const policyId = "<policy_id>";

        // Helper function to set a cookie with security flags
        const setCookie = (name, value, options = {}) => {
            let cookieString = `${name}=${encodeURIComponent(value)}`;

            // Set expiration (default: session cookie, or 1 day if specified)
            if (options.maxAge !== undefined) {
                cookieString += `; Max-Age=${options.maxAge}`;
            }

            // Security flags for best practices
            cookieString += "; Secure"; // Only transmit over HTTPS
            cookieString += "; SameSite=Strict"; // Prevent CSRF attacks

            // Note: HttpOnly flag cannot be set from JavaScript and must be set server-side
            // For full security, consider having your backend endpoint set the cookie

            document.cookie = cookieString;
        };

        // Callback functions
        const successCallback = (response) => {
            console.log(response);
            // Store DaVinci session token in a secure cookie (1 day expiration).
            // The server reads this as "DV-ST" to maintain session continuity.
            setCookie("DV-ST", response.sessionToken, { maxAge: 86400 });
        };

        const errorCallback = (error) => {
            console.log(error);
        };

        const onCloseModal = () => {
            console.log("onCloseModal");
        };

        // Initialize and render the DaVinci widget
        (async () => {
            try {
                // Request a DaVinci SDK token from the backend server.
                // The server handles API key management and session token forwarding
                // so that sensitive credentials are never exposed to the browser.
                const tokenResponse = await fetch("/dvtoken", {
                    method: "POST",
                    headers: {
                        "Content-Type": "application/json",
                    },
                    credentials: "include", // Send cookies (DV-ST) to the server
                    body: JSON.stringify({ policyId }),
                });

                if (!tokenResponse.ok) {
                    const errorData = await tokenResponse.json();
                    throw new Error(errorData.error || "Failed to retrieve DaVinci SDK token from server.");
                }

                // Server returns { token, companyId, apiRoot }
                const { token, companyId, apiRoot } = await tokenResponse.json();

                const props = {
                    config: {
                        method: "runFlow",
                        apiRoot: apiRoot,
                        accessToken: token,
                        includeHttpCredentials: true, //either true or false. Set this to true to share cookies
                        staggerFlowExecutions: false, //Set this to true to allow multiple flow executions, with one active and others paused
                        flowTakeoverWaitTimeSeconds: 5, //The delay before a paused flow begins execution, between 3 and 15.
                        lockAcquisitionTimeoutSeconds: 300, //The timeout value for a paused flow, between 30 and 1200.
                        companyId,
                        policyId
                        // parameters: {
                        //     nonce: "string to validate",
                        // },
                    },
                    useModal: false,
                    successCallback,
                    errorCallback,
                    onCloseModal,
                };

                // Invoke the Widget
                console.log(props);
                davinci.skRenderScreen(
                    document.querySelector(".dvWidget"),
                    props
                );
            } catch (error) {
                console.error("Error initializing DaVinci widget:", error);
            }
        })();
    </script>
</body>

</html>
```

### Server-side JavaScript code

```javascript
const Fastify = require('fastify');
const fastifyCookie = require('@fastify/cookie');
const fastifyCors = require('@fastify/cors');
const fastifyStatic = require('@fastify/static');
const dotenv = require('dotenv');
const path = require('path');

// Load environment variables from .env file (relative to this script's directory)
dotenv.config({ path: path.join(dirname, '.env') });

// Initialize Fastify with logging enabled
const fastify = Fastify({ logger: true });

// Register the @fastify/cookie plugin for request.cookies support
fastify.register(fastifyCookie);

// Register CORS to allow browser requests (credentials: true for cookie support)
fastify.register(fastifyCors, {
  origin: true,
  credentials: true,
});

// Serve static files (HTML, CSS, JS) from the current directory
fastify.register(fastifyStatic, {
  root: path.join(dirname),
  prefix: '/',
});

// Get a dv token from the server, we do this in server.js as a security best practice so
// API Keys don't need to be exposed on the front-end.
fastify.post('/dvtoken', {
  schema: {
    body: {
      type: 'object',
      required: ['policyId'],
      properties: {
        policyId: { type: 'string' },
        apiKey: { type: 'string' },
        companyId: { type: 'string' },
        flowParameters: { type: 'object' },
      },
    },
  },
}, async function (request, reply) {
  // Allow for apiKey and companyId overrides to come from front end, even though it's not encouraged.
  // Optional chaining guards against null/undefined values for optional body properties.
  const apiKey = request.body?.apiKey || process.env.BXI_API_KEY;
  const companyId = request.body?.companyId || process.env.BXI_COMPANY_ID;

  let body = {
    policyId: request.body.policyId,
  };

  if (request.cookies?.['DV-ST']) {
    body.global = {
      sessionToken: request.cookies['DV-ST'],
    };
  }

  if (request.body?.flowParameters) {
    body.parameters = request.body.flowParameters;
  }

  const dvBaseUrl = `${process.env.BXI_API_URL}/`;
  const dvSdkTokenBaseUrl = `${process.env.BXI_SDK_TOKEN_URL}/v1`;

  const tokenRequest = {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-SK-API-KEY': apiKey,
    },
    body: JSON.stringify(body),
  };

  const tokenResponse = await fetch(
    `${dvSdkTokenBaseUrl}/company/${companyId}/sdktoken`,
    tokenRequest
  ); // Endpoint is case sensitive in Davinci V2
  const parsedResponse = await tokenResponse.json();

  if (!parsedResponse?.success) {
    request.log.error('An error occurred getting DaVinci token');
    request.log.error({ parsedResponse }, 'Parsed Response');
    return reply.code(500).send({
      error: `An error occurred getting DaVinci token. See server logs for more details, code: ${parsedResponse?.httpResponseCode}, message: '${parsedResponse?.message}'.`,
    });
  }

  request.log.info('Successfully retrieved sdktoken for DaVinci');

  return reply.send({
    token: parsedResponse.access_token,
    companyId: companyId,
    apiRoot: dvBaseUrl,
  });
});

// Start the server
const start = async () => {
  try {
    const port = process.env.PORT || 3000;
    await fastify.listen({ port, host: '0.0.0.0' });
  } catch (err) {
    fastify.log.error(err);
    process.exit(1);
  }
};

start();
```

## Getting the SDK token

When implementing a DaVinci application integration using the widget method, be aware that the `POST <authPath>/<companyID>/davinci/policy/<davinciFlowPolicyID>/start` request that invokes the flow takes an SDK token to authenticate. However, the call to get a DaVinci SDK token, `GET <orchestratePath>/company/<companyID>/sdktoken`, requires the application's API key to authenticate.

|   |                                                                                                                                                             |
| - | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
|   | The `/sdktoken` call must be executed on the server side, not in client-side code, to protect the application's API key from exposure on a public web page. |

The following sample shows a server-side code snippet from a `server.js` file used to generate the DaVinci SDK token without exposing the application's API key. You can use this in your server-side code if you already have a server-side component. It's not necessary if you're using the example described in the previous section.

|   |                                                                                                                                                                                                                                                                                          |
| - | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|   | The sample won't work unless you add your region-specific information. Replace any instances of *\<region>* with your regional top-level domain:- Use `.com` for North America.

- Use `.ca` for Canada.

- Use `.eu` for EMEA.

- Use `.asia` for APAC.

- Use `.com.au` for Australia. |

```javascript
/************************
* DaVinci components
************************/

// Get a Widget sdkToken
function getDVToken(cb) {
  const url = 'https://orchestrate-api.pingone.<region>/v1/company/${companyId}/sdktoken';
  fetch(url, {
    headers: {
      "X-SK-API-KEY":  <DV API Key>
    },
    method: "GET"
  })
  .then(res => res.json())
  .then(data => cb(data))
  .catch(err => console.log("Error: ", err));
}
```

## Using the widget with a return URL

When using the widget with social providers, PingID, or other services requiring you to supply a return URL, add coding similar to this.

Retrieve the value for a `continueToken`.

```javascript
/**
 * Event listener for window.load()
 * Listening for a query parameter of `continueToken`
 */
window.addEventListener('load', (event) => {
  var urlParams = new URLSearchParams(window.location.search);
  if (urlParams.get('continueToken')) {
    // flush parameter from window url
    window.history.pushState({}, document.title, window.location.pathname);
    continueWidget('policyId', 'authnflow', urlParams.get('continueToken'))
  }
});
```

Invoke the widget and continue the flow using the `continueToken`.

|   |                                                                                                                                                                                                                                                                                                    |
| - | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|   | The following example won't work unless you add your region-specific information. Replace any instances of `<region>` with your regional top-level domain:- Use `.com` for North America.

- Use `.ca` for Canada.

- Use `.eu` for EMEA.

- Use `.asia` for APAC.

- Use `.com.au` for Australia. |

```javascript
/**
 * Recreates an instance of the Widget placed on the page,
 * and uses the provided 'continueToken' value instead of fetching a new one from DaVinci
 * @param {string} policyId - The flow policy ID for the widget to run
 * @param {string} divId - Location on the page to place the Widget
 * @param {string} continueToken - Value of the 'continueToken' query parameter
 */
function continueWidget(policyId, divId, continueToken){
  /**
   * Creates an instance of the Widget with the following:
   * @param {object} props - Properties for the Widget execution
   * @param {object} props.config - Object containing the Widget configuration
   * @param {string} props.config.method - Widget run method { "runFlow" | "continueFlow" }
   * @param {string} props.config.apiRoot - URL of the DaVinci instance for this flow
   * @param {string} props.config.accessToken - @param {string} continueToken
   * @param {string} props.config.companyId - ID of the PingOne environment that contains the flow
   * @param {string} props.config.policyId - Flow policy ID for the Widget to run
   * @param {boolean} props.useModal - Present Widget as a modal, instead of embedded
   * @param {requestCallback} props.successCallback - The callback that handles the Success response
   * @param {requestCallback} props.errorCallback - The callback that handles the Error response
   * @param {requestCallback} props.onCloseModal - The callback that handles the modal close response ('useModal' == true)
   */
  var props= {
    config: {
      method: 'continueFlow',
      apiRoot: 'https://auth.pingone.<region>/',
      accessToken: continueToken,
      companyId:  <companyID>,
      policyId: policyId`
    },
    useModal: false,
    successCallback, errorCallback, onCloseModal
    }
    /*** Invoke DaVinci Widget ****/
    davinci.skRenderScreen(document.getElementById(divId),props)
  }
```

## Limiting the cookies passed by the widget

By default, the widget passes to DaVinci all cookies that could be presented to the origin. There can be a large number of cookies, only some of which are likely to be used for your DaVinci flows. In some cases, this can cause a `431 Error: Request Header Fields Too Large`. To avoid this possibility, you can set the `originCookies` property.

The `originCookies` property is optional and accepts a string array of cookie names. Only the cookies specified will be passed by the widget to DaVinci. Set this as you would any of the DaVinci properties.

|   |                                                                                                                                                                                                                                                                                                    |
| - | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|   | The following example won't work unless you add your region-specific information. Replace any instances of `<region>` with your regional top-level domain:- Use `.com` for North America.

- Use `.ca` for Canada.

- Use `.eu` for EMEA.

- Use `.asia` for APAC.

- Use `.com.au` for Australia. |

```javascript
var props= {
   config: {
     apiRoot: 'https://auth.pingone.<region>/',
     companyId:  <companyID>,
     policyId:  <policyId>,
     originCookies: ["cookie-1", "cookie-2"]
   }
}
```

To disable passing cookies from the widget, set `originCookies` to an empty array.
