> ## 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.

# Getting started

Start your agent integration by embedding the chat into your web application and applying the appropriate security settings for your scenario. This getting started guide is designed for developers who want a clear, straightforward path to adding an agent-driven chat experience to a web application.

<Callout icon="key" color="#8B5CF6" iconType="panel-left-close">If you are looking for an architectural overview of security, see [Securing the embedded chat](https://www.ibm.com/docs/SSAVQO/deploy/web_chat_agent/web_chat_securing.html).</Callout>

## Integrating agents with web applications

<Steps>
  <Step title="Configure security for embedded chat">
    Security is enabled by default in both Draft and Live environments but not configured. You must explicitly configure it as described in the [Configuring security for embedded chat](https://www.ibm.com/docs/SSAVQO/deploy/web_chat_agent/web_chat_configuring_security.html) section.
    The embedded chat does not initialize unless the required cryptographic keys are configured and validated by the service.
  </Step>

  <Step title="Generate the embed script">
    Generate the script to embed the agent with your web application by using the `channels webchat embed` command.\
    If you prefer to generate the script by using the UI, see [Embedding the agent to web applications](https://www.ibm.com/docs/SSAVQO/deploy/web_chat_agent/web_chat_how_embed.html) in the *watsonx Orchestrate* base documentation. The following procedure focuses on the command line-based workflow.

    ```bash theme={null}
    orchestrate channels webchat embed --agent-name=test_agent1
    ```

    Where:

    `agent-name`\
    Assign the name of the agent that you want to get the embed script.

    The previous code outputs a `<script>` tag with configuration for your environment. You can add this script to an HTML page on your website. The following is an example of the command's output:

    ```html OUTPUT [expandable] theme={null}
    <script>
      window.wxOConfiguration = {
        orchestrationID: "your-orgID_orchestrationID", // Adds control over chat display mode (e.g., fullscreen)
        hostURL: "https://dl.watson-orchestrate.ibm.com", // or region-specific host
        rootElementID: "root",
        showLauncher: false,
        deploymentPlatform: "ibmcloud", // Required for IBM Cloud embed, can be skipped for other embed scenarios
        crn: "your-org-crn", // Required for IBM Cloud embed, can be skipped for other embed scenarios. For more information, see https://cloud.ibm.com/docs/key-protect?topic=key-protect-retrieve-instance-ID&interface=ui
        chatOptions: {
          agentId: "your-agent-id",
          agentEnvironmentId: "your-agent-env-id",
        }
      };

      setTimeout(function () {
        const script = document.createElement("script");
        // The '/wxochat' path is included only for production embeds.
        // It is not included for local environment.
        script.src = `${window.wxOConfiguration.hostURL}/wxochat/wxoLoader.js?embed=true`;
        script.addEventListener("load", function () {
          wxoLoader.init();
        });
        document.head.appendChild(script);
      }, 0);
    </script>
    ```

    <Note>
      The `/wxochat` path is environment-specific.

      In the Developer Edition, for local environment, the `/wxochat` prefix is not present.
      In production environments, the `/wxochat` prefix is required and appears in the embed script.
    </Note>
  </Step>

  <Step title="Add the script to your web application">
    To successfully embed and run the agent within the embedded chat, your application must meet the following requirements:

    * Include a server-side component (local or cloud-based)\
      A server is required to securely store and use private keys, sign requests, and interact with watsonx Orchestrate APIs.

    * Include an HTML `root` container\
      The embedding page must contain an HTML element with the ID `root`.

    * Use HTML strict mode\
      The page that embeds the web chat must declare HTML strict mode by including the `<!DOCTYPE html>` directive.

    Place the embedded script inside the `<body>` tag to ensure that the DOM is available when the script runs, and that the root element can be resolved correctly at initialization time.

    ```html theme={null}
    <!DOCTYPE html>
    <body>
      <div id="root"></div>
      <script src="path-to-embed-script.js"></script>
    </body>
    ```
  </Step>
</Steps>

### Example implementation

The following is a minimal implementation example of a server and website running the web chat with security and authentication configured. This implementation uses **NodeJS** with the [Express web application framework](https://expressjs.com/).

The project uses the following folder structure:

```
.
└── webchatProject/
    ├── keys/
    │   ├── example-jwtRS256.key
    │   ├── example-jwtRS256.key.pub
    │   └── ibmPublic.key.pub
    ├── routes/
    │   └── createJWT.js
    ├── static/
    │   └── index.html
    ├── server.js
    └── package.json
```

You can generate the `example-jwtRS256` keys with the following commands:

```bash theme={null}
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
```

And obtain the IBM public key by running the script provided in [Enabling security](#enabling-security).

Use the following file contents as a reference for your implementation:

<CodeGroup>
  ```js createJWT.js [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 demo purposes - in production, use a longer duration
   * such as 45 days (45 * 24 * 60 * 60 * 1000 milliseconds).
   */
  const TIME_45_DAYS = 60; // Demo value - replace with: 45 * 24 * 60 * 60 * 1000 for production

  /**
   * Create a signed JWT string for the wxO embed 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: "FHYU235DD5", // Example value - replace with actual user ID
      
      // User payload: Will be encrypted with IBM's public key
      // This data is sensitive and will only be readable by watsonx Orchestrate servers
      user_payload: {
        name: "Anonymous",
        custom_message: "Encrypted message",
        custom_user_id: "",
        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: "10000000s", // Demo value - use shorter duration in production (e.g., "1h")
    });

    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 demo)
      // In production, you might want to use the full UUID for better uniqueness
      anonymousID = `anon-${uuid().slice(0, 5)}`;
    }

    // Set/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 demo, 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 embed 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 embed 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;
  ```

  ```html index.html [expandable] theme={null}
  <!DOCTYPE html>
  <html lang="en">

  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>wxO embed example – secure (single-init, minimal)</title>

    <script>
      /**
       * This is the function that is called when a new user id is needed.
       */
      function getUserId() {
        let embed_user_id = sessionStorage.embed_user_id;
        if (!embed_user_id) {
          embed_user_id = Math.trunc(Math.random() * 1000000);
          sessionStorage.embed_user_id = embed_user_id;
        }
        return embed_user_id;
      }

      /**
      * Fetches a new JWT identity token from the backend.
      * In production, this should call your secure server endpoint.
      */
      async function getIdentityToken() {
        try {
          const userId = getUserId();
          const response = await fetch(`http://localhost:5555/createJWT?user_id=${userId}`, {
            method: "GET",
            credentials: "include",
          });

          if (!response.ok) {
            console.error(`[wxO Secure Embed] Failed to fetch JWT: ${response.status} ${response.statusText}`);
            return;
          }

          const token = await response.text();

          if (!token) {
            console.error("[wxO Secure Embed] Empty JWT token received.");
            return;
          }

          window.wxOConfiguration.token = token;
          console.log("[wxO Secure Embed] Token fetched successfully.");
        } catch (error) {
          console.error("[wxO Secure Embed] Error fetching identity token:", error);
        }
      }



      /**
       * wxO Configuration object
       * Replace these values with your actual agent configuration from the watsonx Orchestrate console.
       * These are dummy/example values for demonstration purposes only.
       */
      window.wxOConfiguration = {
        // Orchestration ID from your agent's embed code
        orchestrationID: "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX_XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
        
        // Host URL for your watsonx Orchestrate instance
        hostURL: "https://your-instance.watson-orchestrate.ibm.com",
        
        // ID of the HTML element where the chat will be mounted
        rootElementID: "root",
        
        // Chat-specific options
        chatOptions: {
          // Your agent's unique identifier
          agentId: "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
          
          // Your agent's environment identifier
          agentEnvironmentId: "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
        }
      };

      getIdentityToken().then(() => {
        const script = document.createElement("script");
        script.src = `${window.wxOConfiguration.hostURL}/wxochat/wxoLoader.js?embed=true`;
        script.addEventListener("load", function () {
          wxoLoader.init();
        });
        document.head.appendChild(script);
      });
    </script>
  </head>

  <body>
    <!-- Mount node for the chat (fullscreen-overlay/direct render). -->
    <div id="root"></div>

    <!-- Simple demo page content. -->
    <header>
      <h1>Welcome to My Company</h1>
    </header>

    <nav>
      <a href="#">Home</a>
      <a href="#">Services</a>
      <a href="#">About</a>
      <a href="#">Contact</a>
    </nav>

    <section>
      <h2>About Us</h2>
      <p>
        We provide secure, production-grade integrations with IBM watsonx Orchestrate.
        This page is a demo sample of wxO secure embed chat.
      </p>
    </section>

    <section>
      <div class="container">
        <div class="box">
          <h3>Our Services</h3>
          <p>Consulting, integration, and secure identity flows for embedded agent chat.</p>
        </div>
        <div class="box">
          <h3>Why Choose Us?</h3>
          <p>Best practices to prevent duplicate inits and token over-fetching.</p>
        </div>
        <div class="box">
          <h3>Contact Us</h3>
          <p>Talk to us to deploy secure wxO chat in your app.</p>
        </div>
      </div>
    </section>
  </body>

  <style>
    /* Basic, readable styling. Header uses IBM Blue for quick recognition. */
    body {
      font-family: Arial, sans-serif;
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    header {
      background-color: #0f62fe;
      /* IBM Blue */
      color: white;
      padding: 1rem 0;
      text-align: center;
    }

    nav {
      display: flex;
      justify-content: center;
      background-color: #333;
    }

    nav a {
      color: white;
      padding: 14px 20px;
      text-decoration: none;
      text-align: center;
    }

    nav a:hover {
      background-color: #ddd;
      color: black;
    }

    section {
      padding: 2rem;
    }

    .container {
      display: flex;
      flex-wrap: wrap;
      justify-content: space-around;
    }

    .box {
      flex: 1 1 300px;
      margin: 1rem;
      padding: 1rem;
      border: 1px solid #ddd;
      border-radius: 8px;
      background-color: #f9f9f9;
    }

    .box h3 {
      color: #0f62fe;
      /* IBM Blue */
    }

    footer {
      background-color: #333;
      color: white;
      text-align: center;
      padding: 1rem 0;
      position: relative;
      bottom: 0;
      width: 100%;
    }

    @media (max-width: 768px) {
      nav {
        flex-direction: column;
      }
    }
  </style>

  </html>
  ```

  ```js server.js [expandable] theme={null}
  /**
   * This file starts up a basic NodeJS server with express, adds some routes, and adds some error handling. This is
   * mostly boilerplate content that can be adjusted as needed.
   *
   * The server defaults to being available on port 3001.
   */

  const { createServer } = require('http');

  const cookieParser = require('cookie-parser');
  const cors = require('cors');
  const express = require('express');

  const PORT = 5555;

  const app = express();

  // Enable all CORS requests. In a production environment, you will likely want to remove this or set it to something
  // more restrictive.
  app.use(cors({ origin: '*' }));
  app.use(express.json());
  app.use(express.urlencoded({ extended: false }));
  app.use(cookieParser());

  // The routes needed by the application.
  const createJWTRouter = require('./routes/createJWT');

  app.use('/createJWT/', createJWTRouter);

  app.use(express.static('static'));

  // Send a 404 response if no handler was found.
  app.use(function (request, response) {
    response.status(404).send();
  });

  // Error handler.
  app.use(function (error, request, response, next) {
    console.error('An error occurred', error);
    response.status(500).send();
  });

  // Get port from environment and store in Express.
  app.set('port', PORT);

  // Create HTTP server.
  const server = createServer(app);

  // Listen on provided port, on all network interfaces.
  server.listen(PORT);
  server.on('error', onError);
  server.on('listening', onListening);

  /**
   * Event listener for HTTP server "error" event.
   */
  function onError(error) {
    if (error.syscall !== 'listen') {
      throw error;
    }

    // Handle specific listen errors with friendly messages
    switch (error.code) {
      case 'EACCES':
        console.error(`${PORT} requires elevated privileges`);
        process.exit(1);
        break;
      case 'EADDRINUSE':
        console.error(`${PORT} is already in use`);
        process.exit(1);
        break;
      default:
        throw error;
    }
  }

  /**
   * Event listener for HTTP server "listening" event.
   */
  function onListening() {
    console.log(`Listening on ${server.address().port}`);
    console.log(`Open http://localhost:${server.address().port}/index.html in your browser to view the example.`);
  }
  ```
</CodeGroup>

## Using the chat APIs

### Instance methods

Instance methods enable your code to interact with or modify an active chat instance at run time. They provide a way to programmatically control the behavior and state of the embedded chat component after it has been initialized based on user actions, application logic, or external events.

For more information, see [Instance methods](./instance_methods).

### Events

Embedded web chat supports a variety of events that allow you to trigger specific actions or customize behavior.

For more information, see [Events](./events).

### Agent completions API

You can also integrate your agent with external applications by using the ADK's provided agent completions API. These APIs allow agents to be shared across multiple watsonx Orchestrate instances:

* **Orchestrate Native Runs API**: For long-duration workflows.

  *API Documentation*:

  * [Non streaming Runs API](/apis/orchestrate-agent/chat-with-orchestrate-assistant)
  * [Streaming Runs API](/apis/orchestrate-agent/chat-with-orchestrate-assistant-as-stream)

* **Chat Completions Compatibility Layer**: OpenAI-compatible for easy integration.

  *API Documentation*:

  * [Chat Completions API](/apis/orchestrate-agent/chat-with-agents)

## Customizing the chat UI

### Using context variables

To use context variables in embedded chat, include them inside the JWT token. You can add context variables to a JWT token by using a JavaScript script. See [Context variables](./context_variables) for more details.

### Custom styling and layout

The chat supports a flexible layout object that enables you to control how and where the chat UI appears on the page. See [Customizing layout](./customization_ui_configuration#layout) and [Customizing styles](./customization_ui_configuration#styles) for more details.

## Enable feedback

By default, thumbs-up and thumbs-down feedback options are disabled. You need to enable them manually to collect user input on responses. See [Thumbs up and thumbs down feedback](./events_feedback) for more details.
