> ## Documentation Index
> Fetch the complete documentation index at: https://developer.watson-orchestrate.ibm.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Configuring Workday connection on watsonx Orchestrate

In this part, you learn to configure the Workday connection on watsonx Orchestrate by setting the identity provider as Entra ID.

### Before you begin

* Start the Orchestrate ADK

  Install the watsonx Orchestrate Developer Edition with the ADK and activate your watsonx Orchestrate instance. For detailed instructions, see [Using your watsonx Orchestrate account](../../developer_edition/wxOde_setup#using-your-watsonx-orchestrate-account) and [Activating an environment](../../environment/initiate_environment).

## Setting the connection between Workday and watsonx Orchestrate

You can configure Workday connection in two ways:

* [Configuring the Workday connection with file](#configuring-the-workday-connection-with-file)

* [Configuring the Workday connection with CLI commands](#configuring-the-workday-connection-with-cli-commands)

The configuration with file is the recommended approach.

### Configuring the Workday connection with file

Follow the steps to create and import the file:

1. Create a configuration file and name it as `connections.yaml`.

2. Insert the following code into the `connections.yaml` file.

   ```yaml YAML [expandable] theme={null}
   spec_version: v1
   kind: connections
   app_id: <workday-app-id>
   environments:
       draft:
       kind: oauth_auth_on_behalf_of_flow
       type: member
       sso: true
       server_url: https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token
       idp_config:
           header:
           content-type: application/x-www-form-urlencoded
           body:
           requested_token_use: on_behalf_of
           requested_token_type: urn:ietf:params:oauth:token-type:saml2
       app_config:
           header:
           content-type: application/x-www-form-urlencoded

       live:
       kind: oauth_auth_on_behalf_of_flow
       type: member
       sso: true
       server_url: https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token
       idp_config:
           header:
           content-type: application/x-www-form-urlencoded
           body:
           requested_token_use: on_behalf_of
           requested_token_type: urn:ietf:params:oauth:token-type:saml2
       app_config:
           header:
           content-type: application/x-www-form-urlencoded
   ```

3. Replace the following values:

   * `<workday-app-id>` - The ID of the Workday web page that hosts the embedded chat.
   * `{tenant-id}` - The tenant ID of the Microsoft Entra.

4. Run the commands to import the `connections.yaml` file. For more information, see [Creating connections](../../connections/build_connections).

### Configuring the Workday connection with CLI commands

Follow the steps to set the connection:

1. Enter the following code in the terminal:

   ```bash BASH theme={null}
   orchestrate connections configure \
    --app-id <workday-app-id> \
    --env <env> \
    --type team \
    --kind oauth_auth_on_behalf_of_flow \
    --server-url https://example.workday.com/ccx/api/v1/tenant1
   ```

2. Replace the following values:

   * `<workday-app-id>` - The ID of the Workday web page that hosts the embedded chat.
   * `<env>` - The environment type, draft or live.
   * `<token-url>` - Token URL provided in your Workday API registration.

## Configuring the identity provider as Entra ID

1. Enter the following code in the terminal:

   ```bash BASH theme={null}
   orchestrate connections set-identity-provider \
    --app-id <workday-app-id> \
    --env <env> \
    --url https://login.microsoftonline.com/{tenant-id}/token \ 
    --client-id <entra-client-id> \ 
    --client-secret <entra-client-secret> \ 
    --scope http://www.workday.com/user_impersonation \ 
    --grant-type urn:ietf:params:oauth:grant-type:jwt-bearer \ 
    --requested_token_use=on_behalf_of \ --requested_token_type=urn:ietf:params:oauth:token-type:saml2
   ```

   **Note:** If you are using IBM Security Verify as your identity provider, you need to specify only the `grant_type`. You can remove the `requested_token_use` and `requested_token_type` from the code.

2. Replace the following values:

   * `<workday-app-id>` - The ID of the Workday web page that hosts the embedded chat.
   * `<env>` - The environment type, draft or live.
   * `{tenant-id}` - The tenant ID of the Microsoft Entra.
   * `<entra-client-id>` - The Client ID of the Microsoft Entra.
   * `<entra-client-secret>` - The Client Secret of the Microsoft Entra.

## Importing tool

You need to import your tool with the Workday application to watsonx Orchestrate. You can use the `orchestrate tools import` command with the `--app-id` flag to associate the Workday connection with the tool. See the steps:

1. Run the command according to the tool file type:

   Python

   ```bash BASH theme={null}
   orchestrate tools import -k python -f <path-to-file> --app-id <workday-app-id>
   ```

   YAML (OpenAPI tools)

   ```bash BASH theme={null}
   orchestrate tools import -k openapi -f <path-to-file> --app-id <workday-app-id>
   ```

2. Replace the following values:

   * `<workday-app-id>` - The ID of the Workday web page that hosts the embedded chat.
   * `<path-to-file>` - The path, name, and extension of the file

For more information, see \[Associating a connection to a tool].

## Configuring the Workday OAuth credentials

1. Enter the following code in the terminal:

   ```bash BASH theme={null}
   orchestrate connections set-credentials \ 
   --app-id <workday-app-id> \ 
   --env <env> \ 
   --client-id <client-id-workday> \ 
   --token-url <token-url>  \
   --grant-type authorization_code
   ```

2. Replace the following values:

   * `<workday-app-id>` - The ID of the Workday web page that hosts the embedded chat.
   * `<env>` - The environment type, draft or live.
   * `<client-id-workday>` - Client ID provided in your Workday API registration.
   * `<token-url>` - Token URL provided in your Workday API registration.

## Adding watsonx Orchestrate web chat to your application

After the configuration is complete, embed the watsonx Orchestrate web chat to your web page or application. For detailed instructions on embedding the web chat, see [Enabling Secure Embed Flow](../../manage/channels).

To enable SSO, in addition to the embed instructions, complete the following steps:

1. Create the `createJWT.js` and `embed.html` files. You can use the examples that are provided in the following tabs as templates for each file.
2. Save these files in your watsonx Orchestrate repository.

<Tabs>
  <Tab title="createJWT.js">
    ```js JAVASCRIPT [expandable] theme={null}
        /**
       * JWT Creation Service for watsonx Orchestrate Secure Embed Chat
       *
       * This module handles the server-side creation of JSON Web Tokens (JWTs) for secure
       * authentication with IBM watsonx Orchestrate embed chat. It demonstrates:
       *
       * 1. RS256 JWT signing using your private key
       * 2. Optional user payload encryption using IBM's public key
       * 3. Cookie-based anonymous user tracking
       * 4. Session-based user information enrichment
       *
       * Security Notes:
       * - Never expose your private key to the client side
       * - Always generate JWTs server-side only
       * - Use HTTPS in production environments
       * - Adjust token expiration times based on your security requirements
       */

      const fs = require("fs");
      const path = require("path");
      const express = require("express");
      const { v4: uuid } = require("uuid");
      const jwtLib = require("jsonwebtoken");
      const NodeRSA = require("node-rsa");

      const router = express.Router();

      /**
       * Load your server-side RSA private key (PEM format)
       *
       * This key is used to sign JWTs with the RS256 algorithm. The corresponding public key
       * must be uploaded to your watsonx Orchestrate instance security settings.
       *
       * IMPORTANT: Keep this file secure and never expose it to clients!
       *
       * Generate a new key pair using either method:
       *
       * Method 1 - Using ssh-keygen:
       *   ssh-keygen -t rsa -b 4096 -m PEM -f example-jwtRS256.key
       *   openssl rsa -in example-jwtRS256.key -pubout -outform PEM -out example-jwtRS256.key.pub
       *
       * Method 2 - Using openssl directly:
       *   openssl genrsa -out example-jwtRS256.key 4096
       *   openssl rsa -in example-jwtRS256.key -pubout -out example-jwtRS256.key.pub
       */
      const PRIVATE_KEY_PATH = path.join(__dirname, "../keys/example-jwtRS256.key");
      if (!fs.existsSync(PRIVATE_KEY_PATH)) {
        throw new Error(`Private key not found at ${PRIVATE_KEY_PATH}`);
      }
      const PRIVATE_KEY = fs.readFileSync(PRIVATE_KEY_PATH);

      /**
       * Load IBM's RSA public key (PEM format)
       *
       * This key is provided by IBM watsonx Orchestrate and is used to encrypt the user_payload
       * field in the JWT. This ensures that sensitive user information cannot be read by clients,
       * as only IBM's servers can decrypt it.
       *
       * You can obtain this key by:
       * 1. Running the wxo-embed-security-v4.sh script (recommended)
       * 2. Calling the generate-key-pair API endpoint manually
       *
       * The encrypted payload will be decrypted server-side by watsonx Orchestrate.
       */

      const IBM_PUBLIC_KEY_PATH = path.join(__dirname, "../keys/ibmPublic.key.pub");
      if (!fs.existsSync(IBM_PUBLIC_KEY_PATH)) {
        throw new Error(`IBM public key not found at ${IBM_PUBLIC_KEY_PATH}`);
      }
      const IBM_PUBLIC_KEY = fs.readFileSync(IBM_PUBLIC_KEY_PATH);

      /**
       * Cookie lifetime configuration
       *
       * This defines how long the anonymous user ID cookie will persist.
       * Currently set to 60ms for demonstration purposes. For production, use a longer duration
       * such as 45 days (45 * 24 * 60 * 60 * 1000 milliseconds).
       */

      const TIME_45_DAYS = 60; // Value for demonstration. Replace with: 45 * 24 * 60 * 60 * 1000 for production.

      /**
       * Create a signed JWT string for the Orchestrate embedded chat client.
       *
       * This function constructs a JWT with the following structure:
       *
       * @param {string} anonymousUserID - A stable identifier for anonymous users (from cookie)
       * @param {object|null} sessionInfo - Optional authenticated user session data
       *
       * JWT Claims:
       * - sub: Subject (user identifier) - should be a stable, unique ID for the user
       * - user_payload: Encrypted user data that will be decrypted by watsonx Orchestrate
       *   - name: User's display name
       *   - custom_message: Any custom message or metadata
       *   - custom_user_id: Your application's internal user ID
       *   - sso_token: Single sign-on token if applicable
       * - context: Additional context data accessible by the agent
       *   - clientID: Your client/organization identifier
       *   - name: User's name for display in chat
       *   - role: User's role (e.g., Admin, User, Guest)
       *
       * @returns {string} A signed JWT token string
       */
      function createJWTString(anonymousUserID, sessionInfo) {
        // Base JWT claims structure
        // Customize these fields based on your application's requirements
        const jwtContent = {
          // Subject: Unique identifier for the user
          // In production, use a real user ID from your authentication system
          sub: "FHY1234DD5", // Example value. Replace with actual user ID.

          // User payload: It will be encrypted with IBM's public key
          // This data is sensitive and will only be readable by watsonx Orchestrate servers
          // Optional
          user_payload: {
            sso_token: "sso_token",
          },

          // Context: Additional metadata accessible by the agent
          // This data is NOT encrypted and can be read by the client
          context: {
            clientID: "865511",      // Your client/organization ID
            name: "Ava",             // Display name in chat
            role: "Admin",           // User role
          },
        };

        // Enrich the JWT with authenticated session data if available
        // In production, this would come from your authentication system
        if (sessionInfo) {
          jwtContent.user_payload.name = sessionInfo.userName;
          jwtContent.user_payload.custom_user_id = sessionInfo.customUserID;
        }

        // Encrypt the user_payload using IBM's RSA public key
        // This ensures sensitive user data cannot be read by clients
        // Only watsonx Orchestrate servers can decrypt this data
        if (jwtContent.user_payload) {
          const rsaKey = new NodeRSA(IBM_PUBLIC_KEY);
          const dataString = JSON.stringify(jwtContent.user_payload);
          const utf8Data = Buffer.from(dataString, "utf-8");
          // Encrypt and encode as base64 string
          jwtContent.user_payload = rsaKey.encrypt(utf8Data, "base64");
        }

        // Sign the JWT using RS256 algorithm with your private key
        // The token expiration should be set based on your security requirements
        // Common values: "1h" (1 hour), "6h" (6 hours), "1d" (1 day)
        const jwtString = jwtLib.sign(jwtContent, PRIVATE_KEY, {
          algorithm: "RS256",
          expiresIn: "7100s", // Value for demonstration. The maximum expiry time is 7100 seconds.
        });

        return jwtString;
      }

      /**
       * Retrieve or create a stable anonymous user ID stored in a cookie
       *
       * This function ensures that anonymous users maintain a consistent identity across
       * page refreshes and sessions. The ID is stored in a secure HTTP-only cookie.
       *
       * Benefits:
       * - Prevents user identity from changing mid-session
       * - Enables conversation continuity for anonymous users
       * - Provides basic user tracking without requiring authentication
       *
       * @param {object} request - Express request object
       * @param {object} response - Express response object
       * @returns {string} The anonymous user ID
       */
      function getOrSetAnonymousID(request, response) {
        // Check if an anonymous ID already exists in cookies
        let anonymousID = request.cookies["ANONYMOUS-USER-ID"];

        // Generate a new ID if none exists
        if (!anonymousID) {
          // Create a short, readable ID using UUID (first 5 characters for demonstration)
          // In production, you might want to use the full UUID for better uniqueness
          anonymousID = `anon-${uuid().slice(0, 5)}`;
        }

        // Set or refresh the cookie with each request to maintain the session
        response.cookie("ANONYMOUS-USER-ID", anonymousID, {
          expires: new Date(Date.now() + TIME_45_DAYS),
          httpOnly: true,  // Prevents client-side JavaScript from accessing the cookie (security)
          sameSite: "Lax", // Provides CSRF protection while allowing normal navigation
          secure: false,   // Set to true in production when using HTTPS
        });

        return anonymousID;
      }

      /**
       * Parse authenticated session information from cookies
       *
       * This function retrieves user session data if the user is authenticated.
       * In a production application, you would:
       * 1. Verify the session token/cookie
       * 2. Fetch user information from your database or identity provider
       * 3. Validate user permissions
       *
       * For this demonstration, we simply parse a JSON string from a cookie.
       *
       * @param {object} request - Express request object
       * @returns {object|null} Session info object or null if not authenticated
       */
      function getSessionInfo(request) {
        const sessionInfo = request.cookies?.SESSION_INFO;
        if (!sessionInfo) return null;

        try {
          // Parse the JSON session data
          return JSON.parse(sessionInfo);
        } catch {
          // Return null if parsing fails (invalid JSON)
          return null;
        }
      }

      /**
       * Express route handler for JWT creation
       *
       * This endpoint is called by the client to obtain a fresh JWT token.
       * The token is required for secure authentication with watsonx Orchestrate embedded chat.
       *
       * Flow:
       * 1. Retrieve or create an anonymous user ID (stored in cookie)
       * 2. Check for authenticated session information
       * 3. Generate a signed JWT with user data
       * 4. Return the JWT as plain text response
       *
       * The client will include this JWT in the wxOConfiguration.token field
       * when initializing the embedded chat.
       *
       * @param {object} request - Express request object
       * @param {object} response - Express response object
       */
      function createJWT(request, response) {
        // Ensure we have a stable user ID (anonymous or authenticated)
        const anonymousUserID = getOrSetAnonymousID(request, response);

        // Get authenticated session data if available
        const sessionInfo = getSessionInfo(request);

        // Create and sign the JWT
        const token = createJWTString(anonymousUserID, sessionInfo);

        // Return the JWT as plain text
        response.send(token);
      }

      // Define the GET endpoint that returns a signed JWT string
      // This endpoint is called by the client before initializing the chat
      router.get("/", createJWT);

      module.exports = router;
    ```

    Replace the `<wo_tenant_id>` and `<sso_token>` with appropriate information.

    | Parameter        | Description                               |
    | ---------------- | ----------------------------------------- |
    | `<wo_tenant_id>` | Your watsonx Orchestrate tenant ID.       |
    | `<sso_token>`    | The SSO token obtained from the Entra ID. |

    <b>Run the following commands to retrieve the SSO token</b>

    ```bash BASH theme={null}
    curl --location 'https://login.microsoftonline.com/0195ea87-1839-4df0-9739-bf7eec6de925/oauth2/v2.0/token' \
      --header 'Content-Type: application/x-www-form-urlencoded' \
      --header 'Cookie: fpc=ArnSgeQ8FmNJkcInGVBWnw94mWYSAQAAAG0uod8OAAAA; stsservicecookie=estsfd; x-ms-gateway-slice=estsfd' \
      --data-urlencode 'grant_type=password' \
      --data-urlencode 'client_id=<client_id>' \
      --data-urlencode 'client_secret=<client_secret>' \
      --data-urlencode 'scope=api://<client_id>/default' \
      --data-urlencode '<username>' \
      --data-urlencode '<password>'
    ```
  </Tab>

  <Tab title="embed.html">
    ```html HTML [expandable] theme={null}
    <html lang="en">

    <head>
      <title>Watson Orchestrate</title>
    </head>

    <body>
      <script>
        function preSendHandler(event) {
          if (event?.message?.content) {
            event.message.content = event.message.content.toUpperCase();
          }
        }

        function sendHandler(event) {
          console.log('send event', event);
        }

        function preReceiveHandler(event) {
          event?.content?.map((element) => {
            if (element?.text?.includes('assistant')) {
              element.text = element.text.replace('assistant', 'Agent');
            }
          });
        }

        function receiveHandler(event) {
          console.log('received event', event);
        }

        function onUserDefinedContentTypeReceived(userDefinedMessageObject) {
          return ` <cds-code-snippet>
                          node -v Lorem ipsum dolor sit amet, consectetur adipisicing elit. Blanditiis,
                          veritatis voluptate id incidunt molestiae officia possimus, quasi itaque
                          alias, architecto hic, dicta fugit? Debitis delectus quidem explicabo vitae
                          laboriosam!
                        </cds-code-snippet>
                        <br> <br>
                        <div style='background-color:orange;color:white;padding:10px;'>
                            <p>${userDefinedMessageObject.template}</p>
                        </div>
                        `;
        }

        function onLoad(instance) {
          console.log('RDEBUG onLoad in embed script ', instance)
          instance.on('chatstarted', (instance) => {
            window.wxoChatInstance = instance;
          });
          instance.on('pre:send', preSendHandler);
          instance.on('send', sendHandler);
          instance.on('pre:receive', preReceiveHandler);
          instance.on('receive', receiveHandler);
        }

        window.wxOConfiguration = {
          orchestrationID: '<my-tenant-id>',
          hostUrl: '<service_instance_url>',
          rootElementId: 'root',
          layout: 'small',
          showLauncher: false, // Set to false to render the chat window directly
          token: '<sso_token>'
                        chatOptions: {
            onUserDefinedContentTypeReceived: onUserDefinedContentTypeReceived,
            onLoad: onLoad,
            agentId: '<test_agent1>',
            agentEnvironmentId: '<my-agent-live-env-id>',
          },
        };
        setTimeout(function () {
          const script = document.createElement('script');
          // script.src = `${window.wxOConfiguration.hostUrl}/wxoLoader.js?embed=true`;
          script.src = `${window.wxOConfiguration.hostUrl}/wxochat/wxoLoader.js?embed=true`;
          script.addEventListener('load', function () {
            wxoLoader.init();
          });
          document.head.appendChild(script);
        }, 0);
      </script>
      <div id="root">Sample</div>
    </body>

    </html>
    ```

    | Parameter           | Description                                                                                           |
    | ------------------- | ----------------------------------------------------------------------------------------------------- |
    | `<orchestrationID>` | Your watsonx Orchestrate tenant ID.                                                                   |
    | `<hostUrl>`         | The Service instance URL, obtained from <b>Settings > API details </b> in the watsonx Orchestrate UI. |
    | `<token>`           | The SSO token obtained from the watsonx Orchestrate web chat embedded application.                    |

    To retreive the SSO token:

    a. Launch the embedded watsonx Orchestrate web chat.

    b. In the web page or application UI, click Inspect and then refresh the page.

    c. Navigate to the <b>Network</b> tab.

    d. Locate the `sso_token` under the <b>Response</b> tab.
  </Tab>
</Tabs>

## Testing the integration

To test the integration, sign in and interact with Workday through embedded chat.

1. Log in to the web page or application that hosts the embedded chat using your own credentials.
2. Start the watsonx Orchestrate chat widget.
3. Ask a Workday related query in the embedded chat.

## Result

If the SSO authentication is correctly configured, the Workday returns a JSON response with the requested data.

## Troubleshooting

| Issue                        | Possible cause                               | Resolution                              |
| ---------------------------- | -------------------------------------------- | --------------------------------------- |
| `401 Unauthorized`           | Invalid SAML assertion or client credentials | Check your app’s API client credentials |
| `invalid_grant`              | Redirect URI mismatch                        | Verify your IDP app settings            |
| No response in embedded chat | Chat not embedded correctly                  | Verify the script and app ID            |
