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

# Integrating agents with my application

## Embedding the agent into the web chat

IBM watsonx Orchestrate allows you to embed intelligent agents directly into your web applications using the Embedded Chat feature. This integration supports secure communication, flexible UI customization, and advanced event handling to deliver rich conversational experiences.

## Why embed agents?

Embedding agents into your application provides:

* **Real-time interaction**: Users engage with agents directly within your app.
* **Custom UI integration**: Match the agent’s look and feel to your brand.
* **Secure communication**: RSA encryption and JWT authentication protect sensitive data.
* **Context-aware automation**: Agents can access encrypted context variables to personalize responses.
* **Scalable deployment**: Embed across multiple environments using consistent configuration.

## Prerequisites

### Environments: draft vs. live

* **Draft**: Used for testing. Embed snippets target the draft variant.
* **Live**: Available after deployment. Embed snippets default to the live variant in production.

Use the CLI command to generate the embed script:

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

This outputs a `<script>` tag with configuration for your environment.

When targeting the local environment, the command uses your agent's draft variant. On a production instance, it defaults to using your agent's live (deployed) variant. 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 [FAQs](https://www.ibm.com/docs/en/watsonx/watson-orchestrate/base?topic=experience-faqs#how-can-i-know-my-crn-id)
    chatOptions: {
      agentId: "your-agent-id",
      agentEnvironmentId: "your-agent-env-id",
    },
    layout: {
      form: "fullscreen-overlay", // Options: float | custom | fullscreen-overlay - if not specified float is default
      showOrchestrateHeader: true, // Optional: shows top agent header bar
      width: "600px", // Optional: honored when form is float only
      height: "600px", // Optional: honored when form is float only
      showMaxWidth: true // Optional: honored when form is float only. Indicates your chat will be covered in the full width of the float chat
    },
  };

  setTimeout(function () {
    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);
  }, 0);
</script>
```

### Application requirements

Your application must:

* Include a server (local or cloud).
* Have an HTML element with ID `root`.
* Place the embed script inside the `<body>` tag.
* The page that embeds the web chat must use HTML's strict mode (in clude the `<!DOCTYPE html>` tag).

Example:

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

## Security configuration

Security is **enabled by default**, but must be **explicitly configured**. The embedded chat will not function until security is **properly configured**

### Security Architecture

The embedded chat uses **RSA public-key cryptography** to secure communication. The configuration involves two key pairs:

1. IBM Key Pair
   * **Generated by:** watsonx Orchestrate service.
   * **Public key:** Shared with your application. Your application uses this key to encrypt the `user_payload` section of the JWT sent to watsonx Orchestrate.
   * **Private key:** Stored securely by watsonx Orchestrate.cUsed to decrypt the `user_payload` section of the JWT.

2. Client Key Pair
   * **Generated by:** You (or a security configuration tool).
   * **Public key:** Shared with watsonx Orchestrate. Used to verify that JWTs originate from your application.
   * **Private key:** Remains with you and must be stored securely. Used to sign JWTs sent to watsonx Orchestrate.

3. JWT Authentication
   * When security is enabled, your application must:
     * Generate a JWT signed with your **private key** (Client Key Pair).
     * Include the JWT in all requests to the Embedded Chat API. watsonx Orchestrate validates the token using your **public key**.

<Icon icon="check" size={20} /> **When security is enabled**:

* All requests to the Embedded Chat API must include a valid JWT token
* The token must be signed with your private key
* The watsonx Orchestrate service validates the token using your public key
* This prevents unauthorized access to your watsonx Orchestrate instance

<Icon icon="xmark" size={20} /> **When security is disabled**:

* Requests to the Embedded Chat API do not require authentication
* Anyone with access to your web application where chat is Embedded can access your watsonx Orchestrate instance. In addition, your Watson Orchestrate instance allows anonymous authentication to a limited set of Apis, which is required to get your embed chat to work for anonymous users.
* This option should only be used for specific use cases where anonymous chat access is required.
* Ensure your watsonx Orchestrate instance in this case, does not provide access to sensitive data or access to tools configured with functional credentials that access sensitive data.

### Enabling security

<Steps>
  <Step title="Prerequisites">
    * IBM watsonx Orchestrate instance
    * API Key with administrative privileges
    * Service Instance URL from your watsonx Orchestrate instance
    * **On macOS and Linux:** OpenSSL installed on your system (for key generation)
    * Python Installed on your system (for key extraction from APIs)
  </Step>

  <Step title="Get the watsonx Orchestrate security tool">
    Copy the following automated script to configure security:

    ```bash wxO-embed-chat-security-tool.sh [expandable] theme={null}
    #!/usr/bin/env bash
    set -euo pipefail

    # ============================================================
    # IBM watsonx Orchestrate — Embedded Chat Security Helper (v4)
    # Single Bash script for IBM Cloud / AWS / CPD.
    #
    # WHAT THIS DOES
    #  - Turns Embedded Chat security ON or OFF for a single instance.
    #  - When turning security ON:
    #      • Uses your CLIENT PUBLIC key (existing or newly generated).
    #      • Asks watsonx Orchestrate for an IBM PUBLIC key.
    #      • Saves the keys locally in ./keys and updates the instance config.
    #  - When turning security OFF:
    #      • Disables embedded chat security for that instance (anonymous access).
    #  - When viewing:
    #      • Only fetches and prints the current configuration (no changes).
    #
    # IMPORTANT:
    #  - By default, Embedded Chat security is ON but not configured.
    #    In that state, Embedded Chat will NOT work until keys are set,
    #    or security is explicitly disabled for anonymous access.
    #  - Fully configured security requires BOTH:
    #      • An IBM PUBLIC key generated by watsonx Orchestrate.
    #      • Your CLIENT PUBLIC key (from your own key pair).
    #
    # SUPPORTED INSTANCE URL FORMATS
    #  IBM Cloud / AWS:
    #    https://api.<host>/instances/<INSTANCE_ID>
    #
    #  CPD (Cloud Pak for Data):
    #    https://<host>/orchestrate/<CPD_INSTANCE>/instances/<INSTANCE_ID>
    #
    # ARGUMENTS (optional, in order)
    #   1) WXO_API_KEY            (IBM Cloud / AWS only; ignored for CPD)
    #   2) FULL_INSTANCE_API_URL  (Service instance URL as above)
    #
    # ENVIRONMENT VARIABLES (optional)
    #   ACTION=enable|disable|view    (single-shot, no main menu)
    #   IAM_URL=<explicit IAM base>   (Override IAM URL for non-CPD flows)
    #   WXO_API_KEY                   (Same as arg 1, if not given)
    #   FULL_INSTANCE_API_URL         (Same as arg 2, if not given)
    #   API_URL_WITH_INSTANCE         (Legacy name for FULL_INSTANCE_API_URL)
    #   CPD_USERNAME / CPD_PASSWORD   (For CPD login, otherwise prompted)
    #
    # CPD TOKEN:
    #   POST https://<host>/icp4d-api/v1/authorize { username, password }
    #
    # NON-CPD TOKEN:
    #   POST ${IAM_URL}/siusermgr/api/1.0/apikeys/token { apikey }
    # ============================================================

    # ===============================
    # Colors / formatting (best effort)
    # ===============================
    if [[ -t 1 ]]; then
      BOLD='\033[1m'
      GREEN='\033[0;32m'
      YELLOW='\033[0;33m'
      RED='\033[0;31m'
      BLUE='\033[0;34m'
      NC='\033[0m' # No Color
    else
      BOLD='' GREEN='' YELLOW='' RED='' BLUE='' NC=''
    fi

    # Keep original ACTION for non-interactive single-shot runs
    NON_INTERACTIVE_ACTION="${ACTION:-}"

    # ===============================
    # Helpers
    # ===============================

    banner() {
      echo "============================================================"
      echo -e "${BOLD}$1${NC}"
      echo "============================================================"
    }

    need_cmd() {
      if ! command -v "$1" >/dev/null 2>&1; then
        echo -e "${RED}❌ Required command '$1' not found. Please install it and retry.${NC}"
        exit 1
      fi
    }

    prompt_if_empty() {
      local __varname="$1"
      local __prompt="$2"
      if [[ -z "${!__varname:-}" ]]; then
        read -rp "$__prompt" "$__varname"
        export "$__varname"
      fi
    }

    prompt_secret_if_empty() {
      local __varname="$1"
      local __prompt="$2"
      if [[ -z "${!__varname:-}" ]]; then
        read -rs -p "$__prompt" "$__varname"
        echo
        export "$__varname"
      fi
    }

    # Returns base URL with any leading 'api.' stripped from the host.
    strip_leading_api() {
      local in_url="$1"
      if [[ "${in_url}" =~ ^(https?://)([^/]+)(/.*)?$ ]]; then
        local scheme="${BASH_REMATCH[1]}"
        local host="${BASH_REMATCH[2]}"
        host="${host#api.}"
        echo "${scheme}${host}"
        return 0
      fi
      echo "${in_url}"
      return 0
    }

    # Get scheme://host from a full URL
    origin_from_url() {
      local in_url="$1"
      if [[ "${in_url}" =~ ^(https?://[^/]+) ]]; then
        echo "${BASH_REMATCH[1]}"
        return 0
      fi
      echo "${in_url}"
      return 0
    }

    # Read a PEM file and return JSON string (jq -Rs .), normalize line endings
    read_pem_as_json_string() {
      local pem_path="$1"

      if [[ ! -f "$pem_path" ]]; then
        echo -e "${RED}❌ PEM file not found: $pem_path${NC}" >&2
        return 1
      fi
      if [[ ! -r "$pem_path" ]]; then
        echo -e "${RED}❌ PEM file not readable: $pem_path${NC}" >&2
        return 1
      fi

      if grep -qE -- '-----BEGIN (RSA )?PUBLIC KEY-----' "$pem_path"; then
        : # public key — good
      elif grep -qE -- '-----BEGIN (RSA )?PRIVATE KEY-----' "$pem_path"; then
        echo -e "${YELLOW}⚠️  You provided a PRIVATE key PEM. The API expects the PUBLIC key. Continuing…${NC}" >&2
      else
        echo -e "${YELLOW}⚠️  File lacks standard PUBLIC/PRIVATE KEY PEM header. Continuing…${NC}" >&2
      fi

      tr -d '\r' < "$pem_path" | sed '${/^$/d;}' | jq -Rs .
    }

    # Quick help for instance URL
    show_instance_help() {
      echo
      echo -e "${BOLD}How to find your Service instance URL:${NC}"
      echo "1. Open watsonx Orchestrate in your browser."
      echo "2. Click the profile icon in the top-right corner."
      echo "3. Choose \"Settings\" → \"API details\"."
      echo "4. Copy the \"Service instance URL\", which looks like:"
      echo -e "   ${BLUE}https://api.us-south.watson-orchestrate.ibm.com/instances/20250807-1007-4445-5049-459a42144389${NC}"
      echo
      echo "Paste that full URL into this script when prompted."
      echo
    }

    # Print a human-friendly summary of the /config payload
    print_config_summary() {
      local config_json="$1"

      # Detect whether is_security_enabled field exists, and preserve true/false
      local is_enabled
      is_enabled="$(
        echo "${config_json}" \
          | jq -r 'if has("is_security_enabled") then (.is_security_enabled|tostring) else "__missing__" end' 2>/dev/null \
          || echo "__missing__"
      )"

      # Extract keys
      local public_key client_public_key
      public_key="$(echo "${config_json}" | jq -r '.public_key // ""')"
      client_public_key="$(echo "${config_json}" | jq -r '.client_public_key // ""')"

      local has_client="false"
      local has_ibm="false"
      [[ -n "${client_public_key}" ]] && has_client="true"
      [[ -n "${public_key}" ]] && has_ibm="true"

      if [[ "${is_enabled}" == "__missing__" || -z "${is_enabled}" ]]; then
        echo -e "${YELLOW}Could not find is_security_enabled in the response. Raw config is shown above.${NC}"
        return 0
      fi

      # =========================
      # SECURITY DISABLED STATES
      # =========================
      if [[ "${is_enabled}" == "false" ]]; then
        echo -e "Embedded chat security is: ${YELLOW}DISABLED${NC}"

        if [[ "${has_client}" == "true" && "${has_ibm}" == "false" ]]; then
          echo "Security is disabled, but a client public key is still configured."
          echo "This may be a stale or incomplete setup."
          echo
          echo "To clean this up, you can:"
          echo "  • Replace it with the correct client public key, and when you turn security back ON,"
          echo "    generate a new IBM public key; or"
          echo "  • Clear the client public key if you intend to keep anonymous access."
          echo
          echo "When you enable security again, use the IBM public key to encrypt the user_payload"
          echo "in your identity token, and sign the token with the private key that matches your"
          echo "client public key."
          echo
          echo "Disabled security will allow anonymous users to access your embedded chat."
          echo "Anyone who can access your website will be able to open the chat without authentication."
          echo
          echo "Only use disabled security if your use case explicitly requires anonymous access and your"
          echo "watsonx Orchestrate instance does not expose sensitive data or tools configured with"
          echo "functional credentials."
        else
          echo "Anyone who can access your website can open the embedded chat without authentication."
          echo
          echo "Only use disabled security when your use case explicitly requires anonymous access and your"
          echo "watsonx Orchestrate instance does not expose sensitive data or tools configured with"
          echo "functional credentials."
        fi
        return 0
      fi

      # If we got here and it's not "true", just show raw
      if [[ "${is_enabled}" != "true" ]]; then
        echo -e "${YELLOW}Could not interpret is_security_enabled=\"${is_enabled}\". Raw config is shown above.${NC}"
        return 0
      fi

      # =========================
      # SECURITY ENABLED STATES
      # =========================
      echo -e "Embedded chat security is: ${GREEN}ENABLED${NC}"

      if [[ "${has_client}" == "true" && "${has_ibm}" == "true" ]]; then
        # Fully configured: security ON + both keys present
        echo -e "${GREEN}Embedded chat security is enabled and configured.${NC}"
        echo "Security is enabled and both IBM and client key pairs are configured."
        echo
        echo "Update your embed script to pass a valid RS256-signed JWT using the private key"
        echo "that matches your client public key, so watsonx Orchestrate can authenticate users"
        echo "and allow embedded chat to work as expected."

      elif [[ "${has_client}" == "true" && "${has_ibm}" == "false" ]]; then
        # Security ON + client key present + IBM key missing
        echo -e "${GREEN}Embedded chat security is enabled (encryption not configured).${NC}"
        echo "A client public key is configured, but Encrypt sensitive information is not set up."
        echo
        echo "Generate an IBM public key so you can encrypt the user_payload in your identity token"
        echo "and fully enable encrypted embedded chat security."

      elif [[ "${has_client}" == "false" && "${has_ibm}" == "true" ]]; then
        # Security ON + IBM key present + client key missing
        echo -e "${YELLOW}Embedded chat security is enabled but the client public key is missing.${NC}"
        echo "A client public key is required to validate your identity tokens."
        echo
        echo "Paste a valid client public key in the Chat user identity section and update"
        echo "your configuration so watsonx Orchestrate can verify your RS256-signed JWTs."

      elif [[ "${has_client}" == "false" && "${has_ibm}" == "false" ]]; then
        # Security ON + no keys at all
        echo -e "${YELLOW}Embedded chat security is enabled but not fully configured.${NC}"
        echo "Security is currently turned on, but embedded chat will not work until both key"
        echo "pairs are configured:"
        echo "  • An IBM public key generated by watsonx Orchestrate."
        echo "  • Your application’s client public key used to verify signed tokens."
        echo
        echo "To complete configuration, we recommend the following steps:"
        echo "  1) Provide a valid client public key in the Chat user identity section."
        echo "  2) Generate an IBM public key for Encrypt sensitive information."
        echo "  3) Update your embed script to use the IBM public key to encrypt user_payload."
      else
        # Catch-all for any other unusual combination
        echo -e "${YELLOW}Security is enabled, but the key configuration is incomplete or unusual.${NC}"
        echo "Check that both of the following are set correctly:"
        echo "  • IBM public key (for encrypting user_payload)."
        echo "  • Client public key (for verifying your signed JWTs)."
      fi
    }

    # ===============================
    # Dependencies
    # ===============================
    need_cmd curl
    need_cmd jq
    need_cmd sed
    need_cmd openssl
    need_cmd tr
    need_cmd grep

    # ===============================
    # Welcome + optional help
    # ===============================
    banner "IBM watsonx Orchestrate — Embedded Chat Security Helper (v4)"
    echo "This helper manages embedded chat security for a single watsonx Orchestrate instance."
    echo
    echo "By default, embedded chat security is ON but not configured."
    echo "In that state, embedded chat will not work until:"
    echo "  • both IBM and client key pairs are configured, or"
    echo "  • security is explicitly turned OFF to allow anonymous access."
    echo

    read -rp "Do you need help finding your Service instance URL? (y/N): " NEED_HELP
    NEED_HELP="${NEED_HELP:-n}"
    if [[ "${NEED_HELP}" == [Yy] ]]; then
      show_instance_help
    fi

    # ===============================
    # 1) Inputs (URL first), then creds
    # ===============================
    WXO_API_KEY="${1:-${WXO_API_KEY:-}}"
    FULL_INSTANCE_API_URL="${2:-${FULL_INSTANCE_API_URL:-${API_URL_WITH_INSTANCE:-}}}"

    if [[ -z "${FULL_INSTANCE_API_URL}" ]]; then
      banner "watsonx Orchestrate Embedded Chat Security"
      echo "Please provide the Service instance URL from the API details page:"
      echo "  • IBM Cloud / AWS:"
      echo "      https://api.<host>/instances/<INSTANCE_ID>"
      echo "  • CPD:"
      echo "      https://<host>/orchestrate/<CPD_INSTANCE>/instances/<INSTANCE_ID>"
      echo
    fi

    prompt_if_empty FULL_INSTANCE_API_URL "Enter Service instance URL: "

    # ===============================
    # 2) Parse URL, detect CPD vs Non-CPD
    # ===============================
    IS_CPD="false"
    API_ORIGIN="$(origin_from_url "${FULL_INSTANCE_API_URL}")"

    # CPD pattern
    if [[ "${FULL_INSTANCE_API_URL}" =~ ^(https?://[^/]+)/orchestrate/([^/]+)/instances/([^/?#]+) ]]; then
      IS_CPD="true"
      API_URL_BASE="${BASH_REMATCH[1]}/orchestrate/${BASH_REMATCH[2]}"
      CPD_INSTANCE_NAME="${BASH_REMATCH[2]}"
      WXO_INSTANCE_ID="${BASH_REMATCH[3]}"
    # Non-CPD pattern
    elif [[ "${FULL_INSTANCE_API_URL}" =~ ^(https?://[^/]+)/instances/([^/?#]+) ]]; then
      IS_CPD="false"
      API_URL_BASE="${BASH_REMATCH[1]}"
      WXO_INSTANCE_ID="${BASH_REMATCH[2]}"
    else
      echo -e "${RED}❌ Invalid Service instance URL.${NC}"
      echo "   IBM Cloud / AWS example:"
      echo "     https://api.us-south.watson-orchestrate.ibm.com/instances/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
      echo "   CPD example:"
      echo "     https://cpd-cpd-instance-1.apps.example.com/orchestrate/cpd-instance-1/instances/1757991870301390"
      echo "Got: ${FULL_INSTANCE_API_URL}"
      exit 1
    fi

    # Heuristic: also mark CPD if host contains 'cpd' (defensive)
    if [[ "${API_ORIGIN}" == *"cpd"* ]]; then
      IS_CPD="true"
    fi

    banner "Inputs"
    echo "WXO_INSTANCE_ID:   ${WXO_INSTANCE_ID}"
    echo "API_URL_BASE:      ${API_URL_BASE}"
    echo "API_ORIGIN:        ${API_ORIGIN}"
    echo "Detected Platform: $([[ "${IS_CPD}" == "true" ]] && echo "CPD" || echo "IBM Cloud / AWS")"
    echo

    # ===============================
    # 3) Token acquisition (once)
    # ===============================
    if [[ "${IS_CPD}" == "true" ]]; then
      # CPD: username + password -> token at <origin>/icp4d-api/v1/authorize
      banner "Fetching CPD token"
      CPD_USERNAME="${CPD_USERNAME:-}"
      CPD_PASSWORD="${CPD_PASSWORD:-}"
      prompt_if_empty CPD_USERNAME "Enter CPD Username: "
      prompt_secret_if_empty CPD_PASSWORD "Enter CPD Password (hidden): "

      WXO_TOKEN="$(
        curl --fail -sS \
          --request POST \
          --url "${API_ORIGIN}/icp4d-api/v1/authorize" \
          --header 'Content-Type: application/json' \
          --header 'cache-control: no-cache' \
          --data "{\"username\":\"${CPD_USERNAME}\",\"password\":\"${CPD_PASSWORD}\"}" --insecure \
        | jq -r .token
      )"

      if [[ -z "${WXO_TOKEN}" || "${WXO_TOKEN}" == "null" ]]; then
        echo -e "${RED}❌ Failed to fetch CPD token. Check your credentials and host.${NC}"
        exit 1
      fi
      echo -e "${GREEN}✅ CPD token acquired.${NC}"
      echo

    else
      # IBM Cloud / AWS: API key -> IAM token selection
      if [[ -z "${WXO_API_KEY}" ]]; then
        echo "IBM Cloud / AWS flow selected."
        echo "You will be prompted for WXO_API_KEY (input hidden)."
      fi

      prompt_secret_if_empty WXO_API_KEY "Enter WXO_API_KEY (hidden): "

      # Auto-select IAM_URL unless provided
      if [[ -z "${IAM_URL:-}" ]]; then
        API_URL_NOAPI="$(strip_leading_api "${API_URL_BASE}")"
        case "${API_URL_NOAPI}" in
          # IBM Cloud - PROD
          https://au-syd.watson-orchestrate.cloud.ibm.com|\
          https://jp-tok.watson-orchestrate.cloud.ibm.com|\
          https://eu-de.watson-orchestrate.cloud.ibm.com|\
          https://eu-gb.watson-orchestrate.cloud.ibm.com|\
          https://us-south.watson-orchestrate.cloud.ibm.com|\
          https://us-east.watson-orchestrate.cloud.ibm.com|\
          https://ca-tor.watson-orchestrate.cloud.ibm.com)
            IAM_URL="https://iam.platform.saas.ibm.com"
            ;;
          # AWS - PROD
          https://ap-southeast-1.dl.watson-orchestrate.ibm.com|\
          https://ap-south-1.dl.watson-orchestrate.ibm.com|\
          https://eu-central-1.dl.watson-orchestrate.ibm.com|\
          https://dl.watson-orchestrate.ibm.com|\
          https://cio.watson-orchestrate.ibm.com)
            IAM_URL="https://iam.platform.saas.ibm.com"
            ;;
          # GovCloud (mapped to PROD IAM)
          https://origin-us-gov-east-1.watson-orchestrate.prep.ibmforusgov.com)
            IAM_URL="https://iam.platform.saas.ibm.com"
            ;;
          # STAGING / TEST
          https://us-south.watson-orchestrate.test.cloud.ibm.com|\
          https://perfus-south.watson-orchestrate.test.cloud.ibm.com|\
          https://staging-wa.watson-orchestrate.ibm.com|\
          https://wodlperf.watson-orchestrate.ibm.com|\
          https://preprod.dl.watson-orchestrate.ibm.com)
            IAM_URL="https://iam.platform.test.saas.ibm.com"
            ;;
          # DEV
          https://us-south.watson-orchestrate-dev.test.cloud.ibm.com|\
          https://dev-wa.watson-orchestrate.ibm.com|\
          https://dev-fedtest.watson-orchestrate.ibm.com)
            IAM_URL="https://iam.platform.dev.saas.ibm.com"
            ;;
          *)
            echo -e "${YELLOW}⚠️  Unknown API host after normalization: ${API_URL_NOAPI}${NC}"
            echo "   Falling back to PROD IAM."
            IAM_URL="https://iam.platform.saas.ibm.com"
            ;;
        esac

        export IAM_URL
        banner "IAM URL selected"
        echo "Normalized API host: ${API_URL_NOAPI}"
        echo "IAM_URL:             ${IAM_URL}"
        echo
      else
        banner "IAM URL selected (from environment)"
        echo "IAM_URL: ${IAM_URL}"
        echo
      fi

      banner "Fetching IAM token"
      WXO_TOKEN="$(
        curl --fail -sS \
          --request POST \
          --url "${IAM_URL}/siusermgr/api/1.0/apikeys/token" \
          --header 'accept: application/json' \
          --header 'content-type: application/json' \
          --data "{\"apikey\": \"${WXO_API_KEY}\"}" \
        | jq -r .token
      )"

      if [[ -z "${WXO_TOKEN}" || "${WXO_TOKEN}" == "null" ]]; then
        echo -e "${RED}❌ Failed to fetch IAM token. Check your WXO_API_KEY and IAM_URL.${NC}"
        exit 1
      fi
      echo -e "${GREEN}✅ Token acquired.${NC}"
      echo
    fi

    # ===============================
    # 4) Endpoints (CPD vs Non-CPD)
    # ===============================
    if [[ "${IS_CPD}" == "true" ]]; then
      CONFIG_ENDPOINT="${API_URL_BASE}/instances/${WXO_INSTANCE_ID}/v1/embed/secure/config"
      GENKEY_ENDPOINT="${API_URL_BASE}/instances/${WXO_INSTANCE_ID}/v1/embed/secure/generate-key-pair"
    else
      CONFIG_ENDPOINT="${API_URL_BASE}/instances/${WXO_INSTANCE_ID}/v1/embed/secure/config"
      GENKEY_ENDPOINT="${API_URL_BASE}/instances/${WXO_INSTANCE_ID}/v1/embed/secure/generate-key-pair"
    fi

    # ===============================
    # 5) Common action runner (enable/disable/view + verify)
    # ===============================
    run_action() {
      local action="$1"

      if [[ "${action}" == "enable" ]]; then
        banner "Turn ON embedded chat security and configure keys"

        mkdir -p keys

        echo "Choose how to provide the client public key:"
        echo "  1) Use an existing PUBLIC key PEM (path on disk)"
        echo "  2) Generate a NEW RSA 4096 key pair now"
        read -rp "Enter 1 or 2: " key_choice

        local CLIENT_PUBLIC_KEY_JSON=""
        local KEY_CHOICE="${key_choice}"

        case "$key_choice" in
          1)
            read -rp "Enter path to client PUBLIC key PEM (e.g., /path/to/jwtRS256.key.pub): " CLIENT_PUB_PEM_PATH
            CLIENT_PUBLIC_KEY_JSON="$(read_pem_as_json_string "$CLIENT_PUB_PEM_PATH")"
            ;;
          2)
            echo "Generating client RSA key pair in ./keys ..."
            openssl genrsa -out keys/example-jwtRS256.key 4096 >/dev/null 2>&1
            openssl rsa -in keys/example-jwtRS256.key -pubout -out keys/example-jwtRS256.key.pub >/dev/null 2>&1
            CLIENT_PUBLIC_KEY_JSON="$(read_pem_as_json_string "keys/example-jwtRS256.key.pub")"
            echo "🔐 Generated client key files:"
            echo "  • keys/example-jwtRS256.key        (client PRIVATE key — keep this secure)"
            echo "  • keys/example-jwtRS256.key.pub    (client PUBLIC key)"
            ;;
          *)
            echo -e "${RED}❌ Invalid choice.${NC}"
            return 1
            ;;
        esac

        if [[ -z "${CLIENT_PUBLIC_KEY_JSON:-}" || "${CLIENT_PUBLIC_KEY_JSON}" == "null" ]]; then
          echo -e "${RED}❌ Could not prepare client PUBLIC key for payload.${NC}"
          return 1
        fi

        echo "Requesting IBM public key from watsonx Orchestrate..."
        curl --fail -sS \
          --request POST \
          --url "${GENKEY_ENDPOINT}" \
          --header "Authorization: Bearer ${WXO_TOKEN}" --insecure \
        | jq -r '.public_key' > keys/ibmPublic.key.pub

        if [[ ! -s keys/ibmPublic.key.pub ]]; then
          echo -e "${RED}❌ Failed to retrieve IBM public key.${NC}"
          return 1
        fi

        local IBM_PUBLIC_KEY_JSON
        IBM_PUBLIC_KEY_JSON="$(read_pem_as_json_string "keys/ibmPublic.key.pub")"

        local CONFIG_PAYLOAD
        CONFIG_PAYLOAD=$(
          cat <<JSON
    {
      "public_key": ${IBM_PUBLIC_KEY_JSON},
      "client_public_key": ${CLIENT_PUBLIC_KEY_JSON},
      "is_security_enabled": true
    }
    JSON
        )

        echo "Updating embedded chat security configuration..."
        local RESULT
        RESULT="$(
          curl --fail -sS \
            --request POST \
            --url "${CONFIG_ENDPOINT}" \
            --header "Authorization: Bearer ${WXO_TOKEN}" \
            --header 'Content-Type: application/json' \
            --data "${CONFIG_PAYLOAD}" --insecure
        )"

        echo -e "${GREEN}✅ Embedded chat security has been turned ON and keys are configured (server response below).${NC}"
        echo "Server response:"
        echo "${RESULT}" | jq .
        echo

        echo "Saved IBM PUBLIC key to: keys/ibmPublic.key.pub"
        if [[ "${KEY_CHOICE}" == "1" ]]; then
          echo "Use this IBM public key to encrypt the user_payload in your identity token."
        else
          echo "Use:"
          echo "  • keys/example-jwtRS256.key        (client PRIVATE key for signing JWTs)"
          echo "  • keys/example-jwtRS256.key.pub    (client PUBLIC key configured on the instance)"
          echo "  • keys/ibmPublic.key.pub           (IBM PUBLIC key for encrypting user_payload)"
        fi
        echo

      elif [[ "${action}" == "disable" ]]; then
        # For interactive runs (no NON_INTERACTIVE_ACTION), ask for confirmation
        if [[ -z "${NON_INTERACTIVE_ACTION}" ]]; then
          echo
          echo -e "${RED}Disable embedded chat security?${NC}"
          echo "Disabling security will allow anonymous users to access your embedded chat."
          echo "Anyone who can access your website will be able to open the chat without authentication."
          echo
          echo "Only disable security if your use case explicitly requires anonymous access and your"
          echo "watsonx Orchestrate instance does not expose sensitive data or tools configured with"
          echo "functional credentials."
          echo
          read -rp "Type 'yes' to confirm disabling security: " CONFIRM_DISABLE
          if [[ "${CONFIRM_DISABLE}" != "yes" ]]; then
            echo "Operation cancelled. Security settings were not changed."
            return 0
          fi
        fi

        banner "Turn OFF embedded chat security (anonymous access)"

        local DISABLE_PAYLOAD
        DISABLE_PAYLOAD='{"is_security_enabled": false}'

        echo "Updating embedded chat security configuration..."
        local RESULT
        RESULT="$(
          curl --fail -sS \
            --request POST \
            --url "${CONFIG_ENDPOINT}" \
            --header "Authorization: Bearer ${WXO_TOKEN}" \
            --header 'Content-Type: application/json' \
            --data "${DISABLE_PAYLOAD}" --insecure
        )"

        echo -e "${YELLOW}✅ Embedded chat security has been turned OFF for this instance.${NC}"
        echo "Server response:"
        echo "${RESULT}" | jq .
        echo

      else
        banner "View current embedded chat security configuration (read-only)"
      fi

      # Common: GET /config and summary
      banner "Check current configuration"
      local VERIFY
      VERIFY="$(
        curl --fail -sS \
          --request GET \
          --url "${CONFIG_ENDPOINT}" \
          --header "Authorization: Bearer ${WXO_TOKEN}" \
          --header 'Accept: application/json' --insecure
      )"
      echo "Raw configuration from the service:"
      echo "${VERIFY}" | jq .
      echo
      echo "Summary:"
      print_config_summary "${VERIFY}"
      echo
      echo -e "${GREEN}Action completed.${NC}"
    }

    # ===============================
    # 6) Non-interactive (ACTION set) or interactive main menu loop
    # ===============================
    if [[ -n "${NON_INTERACTIVE_ACTION}" ]]; then
      # Single-shot mode for automation: just run the given action and exit.
      run_action "${NON_INTERACTIVE_ACTION}"
      exit 0
    fi

    # Interactive main menu loop
    while true; do
      echo
      echo "What would you like to do for this instance?"
      echo "  1) Turn ON embedded chat security and configure keys (recommended)"
      echo "  2) Turn OFF embedded chat security and allow anonymous access"
      echo "  3) View current embedded chat security configuration (no changes)"
      echo "  4) Exit"
      read -rp "Enter 1, 2, 3 or 4: " choice

      case "$choice" in
        1)
          run_action "enable"
          ;;
        2)
          run_action "disable"
          ;;
        3)
          run_action "view"
          ;;
        4)
          echo "Exiting."
          break
          ;;
        *)
          echo -e "${RED}❌ Invalid choice. Please enter 1, 2, 3 or 4.${NC}"
          ;;
      esac
    done
    ```
  </Step>

  <Step title="Change the script's permissions">
    On Unix-based systems (macOS and Linux), change the permissions to run the script:

    ```bash theme={null}
    chmod +x wxO-embed-chat-security-tool.sh
    ```
  </Step>

  <Step title="Run the script">
    Run the script and follow the instructions to enable or disable security:

    ```bash theme={null}
    ./wxO-embed-chat-security-tool.sh
    ```
  </Step>
</Steps>

**After you configure security:**

1. The tool generates an IBM key pair via the API
2. The tool generates a client key pair using OpenSSL
3. Both public keys are configured in the service
4. Security is enabled

All keys are saved in the `wxo_security_config` directory:

* `ibm_public_key.pem`: IBM's public key in PEM format
* `ibm_public_key.txt`: IBM's public key in single-line format
* `client_private_key.pem`: Your private key (keep it secure!)
* `client_public_key.pem`: Your public key in PEM format
* `client_public_key.txt`: Your public key in single-line format

### Anonymous Access

You may disable security for anonymous access, but only if:

* No sensitive data is exposed.
* No tools with functional credentials are accessible.

## Context variables

To use context variables:

1. Enable `context_access_enabled: true` in your agent definition.
2. Add variables like `channel` to the `context_variables` in your agent [definition file](./build_agent).
3. Reimport the agent.
4. Include context variables in the JWT payload.

```yaml theme={null}
spec_version: v1
style: react
name: hello_agent
llm: groq/openai/gpt-oss-120b
description:  'Agent description'
instructions: |
  You are a helpful agent that must answer user questions in a clean and concise manner. Be polite and do not perform harmful behavior.
collaborators: []
tools: []
context_access_enabled: true
context_variables:
  - channel     # Use it to get access to context variables on the embedded chat
```

You can add context variables to a JWT token using a JavaScript script. The following script shows how to include context variables inside a JWT token in your server:

```javascript createJWT [expandable] theme={null}
const fs = require('fs');
const RSA = require('node-rsa');
const crypto = require('crypto');
const jwtLib = require('jsonwebtoken');
const express = require('express');
const path = require('path');
const { v4: uuid } = require('uuid');

const router = express.Router();

// This is your private key that you will keep on your server. This is used to sign the jwt. You will paste your public
// key into the appropriate field on the Security tab of the web chat settings page.
// This public key is used to validate the signature on the jwt.
const PRIVATE_KEY = fs.readFileSync(path.join(__dirname, 'wxo_security_config/client_private_key.pem'));

//The code below will use this key to encrypt the user payload inside the JWT.
const PUBLIC_KEY = fs.readFileSync(path.join(__dirname, 'wxo_security_config/client_public_key.pem'));


// A time period of 45 days in milliseconds.
const TIME_45_DAYS = 1000 * 60 * 60 * 24 * 45;

/**
 * Generates a signed JWT. The JWT used here will always be assigned a user ID using the given anonymous user ID. If
 * the user is authenticated and we have session info, then info about the user will also be added to the JWT.
 * Always use the anonymous user ID even if the user is authenticated because changing the user ID in the middle of
 * a session is not allowed.
 */
function createJWTString(anonymousUserID, sessionInfo,context) {
  // This is the content of the JWT. You would normally look up the user information from a user profile.
  const jwtContent = {
    // This is the subject of the JWT which will be the ID of the user.
    //
    // This user ID will be available under integrations.channel.private.user.id in dialog and
    // system_integrations.channel.private.user.id in actions.
    sub: anonymousUserID,
    // This object is optional and contains any data you wish to include as part of the JWT. This data will be
    // encrypted using the public key so it will not be visible to your users.
    user_payload: {
      custom_message: 'Encrypted message',
      name: 'Anonymous',
    },
    context
  };

  // If the user is authenticated, then add the user's real info to the JWT.
  if (sessionInfo) {
    jwtContent.user_payload.name = sessionInfo.userName;
    jwtContent.user_payload.custom_user_id = sessionInfo.customUserID;
  }

  const dataString = JSON.stringify(jwtContent.user_payload);

  // Encrypt the data
  const encryptedBuffer = crypto.publicEncrypt(
    {
      key: PUBLIC_KEY,
      padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
      oaepHash: 'sha256'  // Specify OAEP padding with SHA256
    },
    Buffer.from(dataString, 'utf-8')
  );

  // Convert encrypted data to base64
  jwtContent.user_payload = encryptedBuffer.toString('base64');
  console.log(jwtContent.user_payload)


  // Now sign the jwt content to make the actual jwt. We are giving this a very short expiration time (10 seconds)
  // to demonstrate the web chat capability of fetching a new token when it expires. In a production environment,
  // you would likely want to set this to a much higher value or leave it out entirely.
  const jwtString = jwtLib.sign(jwtContent, PRIVATE_KEY, {
    algorithm: 'RS256',
    expiresIn: '10000000s',
  });

  return jwtString;
}

/**
 * Gets or sets the anonymous user ID cookie. This will also ensure that an existing cookie is updated with a new 45
 * day expiration time.
 */
function getOrSetAnonymousID(request, response) {
  let anonymousID = request.cookies['ANONYMOUS-USER-ID'];
  if (!anonymousID) {
    // If we don't already have an anonymous user ID, then create one. Normally you would want to use a full UUID,
    // but for the sake of this example we are going to shorten it to just five characters to make them easier to read.
    anonymousID = `anon-${uuid().substr(0, 5)}`;
  }

  // Here we set the value of the cookie and give it an expiration date of 45 days. We do this even if we already
  // have an ID to make sure that we update the expiration date to a new 45 days.
  response.cookie('ANONYMOUS-USER-ID', anonymousID, {
    expires: new Date(Date.now() + TIME_45_DAYS),
    httpOnly: true,
  });

  return anonymousID;
}

/**
 * Returns the session info for an authenticated user.
 */
function getSessionInfo(request) {
  // Normally the cookie would contain a session token that we would use to look up the user's info from something
  // like a database. But for the sake of simplicity in this example the session cookie directly contains the user's
  // info.
  const sessionInfo = request.cookies.SESSION_INFO;
  if (sessionInfo) {
    return JSON.parse(sessionInfo);
  }
  return null;
}

/**
 * Handles the createJWT request.
 */
function createJWT(request, response) {
  const anonymousUserID = getOrSetAnonymousID(request, response);
  const sessionInfo = getSessionInfo(request);

  const context = {
    dev_id: 23424,
    dev_name: "Name",
    is_active: true
  }

  response.send(createJWTString(anonymousUserID, sessionInfo,context));
}

router.get('/', createJWT);

module.exports = router;
```

You can check the [examples for watsonx Assistant web chat](https://github.com/watson-developer-cloud/assistant-toolkit/tree/master/integrations/webchat/examples/web-chat-security-complex) that are mostly compatible with the watsonx Orchestrate embedded chat to see how to use this code example

After generating the JWT token, pass it to the embedded web chat. The following example shows how to do that:

```javascript JavaScript [expandable] theme={null}
<script>
    function getUserId() {
        let embed_user_id = getCookie('embed_user_id');
        if (!embed_user_id) {
            embed_user_id = Math.trunc(Math.random() * 1000000);
            setCookie('embed_user_id', embed_user_id);
        }
        return embed_user_id;
    }

    function getCookie(name) {
        console.log('getCookie');
        const value = `; ${document.cookie}`;
        const parts = value.split(`; ${name}=`);
        if (parts.length === 2) return parts.pop().split(';').shift();
    }

    function setCookie(name, value) {
        document.cookie = `${name}=${value}; path=/`;
    }
    function preSendHandler(event) {
        if (event?.message?.content) {
            event.message.content = event.message.content.toUpperCase();
        }
    }

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

    function feedbackHandler(event) {
        console.log('feedback', event);
    }

    function preReceiveHandler(event) {
        event?.content?.map((element) => {
            element.type = 'date';
        });
    }

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

    function userDefinedResponseHandler(event) {
        console.log('userDefinedResponse event', event);
        event.hostElement.innerHTML = `
                        <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>${event.contentItem?.template || '[No message content]'}</p>
                        </div>`;
    }

    function onChatLoad(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);
        instance.on('feedback', feedbackHandler);
        instance.on('userDefinedResponse', userDefinedResponseHandler);
    }
    async function getIdentityToken() {
        // This will make a call to your server to request a new JWT.
        const result = await fetch(
            "http://localhost:3000/createJWT?user_id=" + getUserId()
        );
        window.wxOConfiguration.token = await result.text();
    }

    window.wxOConfiguration = {
        orchestrationID: "20250430-0912-2925-309a-35c6bef54760_20250430-0949-0287-00f2-33dc583100c9",
        hostURL: "https://us-south.watson-orchestrate.cloud.ibm.com",
        rootElementID: "root",
        deploymentPlatform: "ibmcloud",
        crn: "crn:v1:bluemix:public:watsonx-orchestrate:us-south:a/123-456::",
        chatOptions: {
            agentId: "852431a8-32dd-4925-8cc3-9ea3d3162726",
            agentEnvironmentId: "5d769a04-9445-4768-a687-710d6e9a24cf",
            onLoad: onChatLoad
        },
    };
    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>
```

## Customizing the embedded chat

### Header

Header is an optional property in `wxOConfiguration` that controls whether header actions appear.

| Parameter                 | Type    | Description                                                                               |
| ------------------------- | ------- | ----------------------------------------------------------------------------------------- |
| `header.showResetButton`  | boolean | Displays the Reset Chat button in the header when set to true. Default is `true`.         |
| `header.showAiDisclaimer` | boolean | Displays the AI disclaimer icon/button in the header when set to true. Default is `true`. |

### Language

| Parameter       | Type   | Description                                                                                                           |
| --------------- | ------ | --------------------------------------------------------------------------------------------------------------------- |
| `defaultLocale` | string | Defines the default language supported by the chat. Supported values: `de`, `en`, `es`, `fr`, `it`, `ja`, and `pt-BR` |

### Styles

You can customize embedded web chats to create a unique chat interface that better fits your webpage.

To apply custom styles, add a `style` component inside the `window.wxOConfiguration` object in your web chat script. In this component, you can configure the following elements:

Header is an optional property of `wxOConfiguration` that controls the visibility of header actions.

| Parameter                    | Type    | Description                                                           |
| ---------------------------- | ------- | --------------------------------------------------------------------- |
| `headerColor`                | string  | Set a six-digit hex code that defines the chat header color.          |
| `userMessageBackgroundColor` | string  | Set a six-digit hex code that defines the user message bubble color.  |
| `primaryColor`               | string  | Set a six-digit hex code that defines the interactive elements color. |
| `showBackgroundGradient`     | boolean | Displays the background gradient when set to true. Default is `true`. |

The following is an example of how to customize the embedded web chat using the `style` component inside `window.wxOConfiguration`:

<CodeGroup>
  ```html Basic customization [expandable] theme={null}
  <script>
  window.wxOConfiguration = {
    orchestrationID: "my-tenant-id",
    hostURL: "my-host-url",
    rootElementID: "root",            // fullscreen-overlay only
    showLauncher: false,              // fullscreen-overlay only, false = direct render, true = launcher bubble
    chatOptions: {
      agentId: "12345_test_agent1",            // required
      agentEnvironmentId: "my-agent-env-id"    // required
    },
    header: {
      showResetButton: true,   // optional; defaults to true
      showAiDisclaimer: true   // optional; defaults to true
    },
     style: {
     headerColor: '', //6-digit hex value or empty for default
     userMessageBackgroundColor: '', //6-digit hex value or empty for default
     primaryColor: '', //6-digit hex value or empty for default
     showBackgroundGradient: true, // optional; defaults to true
    },
  };

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

  ```html Customize header and style [expandable] theme={null}
  <script>
  window.wxOConfiguration = {
    orchestrationID: "my-tenant-id",
    hostURL: "my-host-url",
    rootElementID: "root",            // fullscreen-overlay only
    showLauncher: false,              // fullscreen-overlay only, false = direct render, true = launcher bubble
    chatOptions: {
      agentId: "12345_test_agent1",            // required
      agentEnvironmentId: "my-agent-env-id"    // required
    },
    header: {
      showResetButton: true,   // optional; defaults to true
      showAiDisclaimer: true   // optional; defaults to true
    },
     style: {
     headerColor: '', //6-digit hex value or empty for default
     userMessageBackgroundColor: '', //6-digit hex value or empty for default
     primaryColor: '', //6-digit hex value or empty for default
     showBackgroundGradient: true, // optional; defaults to true
    },
  };

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

### Layout

The watsonx Orchestrate embed supports a flexible `layout` object to control how and where the chat UI appears.

| Parameter                      | Type        | Default | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| ------------------------------ | ----------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `rootElementID`                | string      | —       | (`fullscreen-overlay` only) ID of the container node to mount chat into.                                                                                                                                                                                                                                                                                                                                                                                     |
| `showLauncher`                 | boolean     | true    | (`fullscreen-overlay` only) Show the bubble launcher (true) or render chat immediately (false).                                                                                                                                                                                                                                                                                                                                                              |
| `layout.form`                  | string      | float   | Defines the layout form of your web chat.<br /><br />Use `fullscreen-overlay` to display the web chat in fullscreen mode. No additional parameters are required.<br /><br />Use `float` to display the web chat as a floating window. Also configure:<ul><li>`width`: Width of the web chat</li><li>`height`: Height of the web chat</li></ul>Use `custom` to define a custom layout. Also configure the `customElement` parameter with your custom element. |
| `layout.width`                 | string      | —       | (float only) Popup width (e.g. '350px', '30rem').                                                                                                                                                                                                                                                                                                                                                                                                            |
| `layout.height`                | string      | —       | (float only) Popup height (e.g. '500px', '40rem').                                                                                                                                                                                                                                                                                                                                                                                                           |
| `layout.showOrchestrateHeader` | boolean     | true    | Render the standard header bar (true) or hide it (false).                                                                                                                                                                                                                                                                                                                                                                                                    |
| `layout.customElement`         | HTMLElement | —       | element reference to render into.                                                                                                                                                                                                                                                                                                                                                                                                                            |

```html [expandable] theme={null}
<script>
window.wxOConfiguration = {
  orchestrationID: "my-tenant-id",
  hostURL: "my-host-url",
  rootElementID: "root",            // fullscreen-overlay only
  showLauncher: false,              // fullscreen-overlay only, false = direct render, true = launcher bubble

  chatOptions: {
    agentId: "12345_test_agent1",            // required
    agentEnvironmentId: "my-agent-env-id"    // required
  },

  layout: {
    form: 'float',                           // 'fullscreen-overlay' | 'float' | 'custom'
    width: '600px',                          // float only
    height: '600px',                         // float only
    showOrchestrateHeader: true,            // hide header if false
    customElement: hostElement              // custom only
  }
};

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

The following is an example of how to customize the layout of the embedded web chat to display it in fullscreen mode:

```html JavaScript [expandable] theme={null}
<script>
window.wxOConfiguration = {
  orchestrationID: "my-tenant-id",
  hostURL: "my-host-url",
  rootElementID: "root",
  showLauncher: false,
  chatOptions: {
    agentId: "test_agent1",
    agentEnvironmentId: "my-agent-draft-env-id"
  },

  layout:{
    form: 'fullscreen-overlay',
    showOrchestrateHeader: true,
  }

};

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

***

## Feedback, events and instance methods

### Thumbs-up and thumbs-down feedback

In the embedded chat, you need to manually enable thumbs-up and thumbs-down feedback using `pre:receive` handlers. First, subscribe to the `pre:receive` event to inject feedback options. Then, handle submitted feedback through the `feedback` event.

The following script shows how to configure feedback in the embedded chat:

```javascript JavaScript [expandable] theme={null}
<script>
function feedbackHandler(event) {
    console.log('feedback', event);
}

function preReceiveHandler(event) {
    console.log('pre-receive event', event);
    const lastItem = event?.message?.content?.[event.message.content.length - 1];
    if (lastItem) {
        lastItem.message_options = {
            feedback: {
                is_on: true,
                show_positive_details: false,
                show_negative_details: true,
                // Note, these positive details are not used as long as show_positive_details is false.
                positive_options: {
                    categories: ['Funny', 'Helpful', 'Correct'],
                    disclaimer: "Provide content that can be shared publicly.",
                },
                negative_options: {
                    categories: ['Inaccurate', 'Incomplete', 'Too long', 'Irrelevant', 'Other'],
                    disclaimer: "Provide content that can be shared publicly.",
                },
            },
        };
    }
}

function onChatLoad(instance) {
    instance.on('pre:receive', preReceiveHandler);
    instance.on('feedback', feedbackHandler);
}

window.wxOConfiguration = {
    ...
        };
setTimeout(function () {
            ...
        }, 0);
</script>
```

watsonx Orchestrate does not persist feedback internally. You are responsible for storage and analysis. If you want to store feedback on your backend, you can capture feedback data and send it to your own backend API:

```javascript sendFeedbackToBackend.js theme={null}
instance.on('feedback', (feedbackEvent) => {
  fetch('/api/store-feedback', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${jwtToken}`
    },
    body: JSON.stringify({
      feedback: feedbackEvent.feedback,
      messageId: feedbackEvent.messageId,
      sessionId: feedbackEvent.sessionId
    })
  });
});

```

### Events

Embedded web chat supports a variety of events that allow you to trigger specific actions or customize behavior. The following tables list all supported events, grouped by category.

#### Customization Events

| Event name            | Description                                                                         |
| --------------------- | ----------------------------------------------------------------------------------- |
| `userDefinedResponse` | Triggered when a response contains an unrecognized or `user_defined` response type. |

#### Message Events

| Event name                | Description                                                                                                                                                                     |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `pre:receive`             | Triggered before the web chat receives a response from the agent.                                                                                                               |
| `pre:send`                | Triggered before the web chat sends a message to the agent.                                                                                                                     |
| `receive`                 | Triggered after the web chat receives a response from the agent.                                                                                                                |
| `send`                    | Triggered after the web chat sends a message to the agent.                                                                                                                      |
| `pre:restartConversation` | Triggered before the conversation restarts. Useful for alerting the user that the chat will reset, allowing them to complete any ongoing actions (e.g., finishing a tool call). |
| `restartConversation`     | Triggered after the conversation restarts, before a new session begins. Useful for displaying specific UI elements when a new session starts.                                   |

#### View Events

| Event name        | Description                              |
| ----------------- | ---------------------------------------- |
| `view:pre:change` | Triggered before the view state changes. |
| `view:change`     | Triggered after the view state changes.  |

#### Security Events

| Event name             | Description                                                   |
| ---------------------- | ------------------------------------------------------------- |
| `identityTokenExpired` | Triggered when security is enabled and the JWT token expires. |

#### Miscellaneous Events

| Event name   | Description                                                                                                                                         |
| ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| `chat:ready` | Triggered when the web chat is fully loaded and ready to receive user input. Useful for displaying a welcome message or initializing UI components. |

### Instance methods

While events are used to listen to the embedded chat, instance methods are used to take actions on the embedded chat. The following tables list all supported instance methods, grouped by category.

#### Message

| Event name                 | Description                                                                                                                                                                     |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `doAutoScroll`             | Scrolls to the most recent message in the list.                                                                                                                                 |
| `scrollToMessage`          | Scrolls the messages list to a specific message.                                                                                                                                |
| `send`                     | Sends the specified message to the agent.                                                                                                                                       |
| `updateHistoryUserDefined` | Updates a user\_defined property in message history. Need to store the `user_defined` in the `message_state` column of the messages table so that it will appear in the history |
| `restartConversation`      | Restarts the conversation with the agent.                                                                                                                                       |

#### User Interface

| Event name     | Description                                                                                      |
| -------------- | ------------------------------------------------------------------------------------------------ |
| `changeView`   | Changes the current view state.                                                                  |
| `updateLocale` | Changes the display language. Supported values: `de`, `en`, `es`, `fr`, `it`, `ja`, and `pt-BR`. |

#### Security/Identity

| Event name            | Description                                                                                                                                             |
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `updateIdentityToken` | Replaces the current JWT with a new one for continued secure communication. This is commonly used when tokens expire or are refreshed during a session. |
| `destroy`             | Destroys the web chat and removes it from the page.                                                                                                     |

#### Events

| Event name | Description                                                                                                                                    |
| ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `once`     | Subscribes a handler function to an event so it runs only once when that event occurs. After the event fires, the handler is no longer called. |
| `off`      | Removes a subscription to an event type.                                                                                                       |
| `on`       | Subscribes to a type of event.                                                                                                                 |

#### Events and instance methods example

The following example shows how to configure events and instance methods in the embedded web chat:

```javascript JavaScript [expandable] theme={null}
    <script>
        function preSendHandler(event, instance) {
            console.log('pre:send event', event);
            if (event?.message?.message?.content) {
                event.message.message.content = event.message.message?.content.toUpperCase();
            }
        }

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

        function feedbackHandler(event, instance) {
            console.log('feedback', event);
        }

        function preReceiveHandler(event, instance) {
            console.log('pre-receive event', event);
            event?.message?.content?.map((element) => {
                if (element?.text?.includes('assistant')) {
                    element.text = element.text.replace('assistant', 'Agent');
                }
                element.type = 'user_defined';
            });

            const lastItem = event?.message?.content?.[event.message?.content.length - 1];
            if (lastItem) {
                lastItem.message_options = {
                    feedback: {
                        is_on: true,
                        show_positive_details: false,
                        show_negative_details: true,

                        positive_options: {
                            categories: ['Funny', 'Helpful', 'Correct'],
                            disclaimer: "Provide content that can be shared publicly.",
                        },
                        negative_options: {
                            categories: ['Inaccurate', 'Incomplete', 'Too long', 'Irrelevant', 'Other'],
                            disclaimer: "Provide content that can be shared publicly.",
                        },
                    },
                };
            }
        }

        function receiveHandler(event, instance) {
            console.log('received event', event);
            instance.off('pre:receive', preReceiveHandler);
            instance.updateAuthToken("wrong-or-expired-token")
        }

        function userDefinedResponseHandler(event, instance) {
            console.log('userDefinedResponse event', event);
            event.hostElement.innerHTML = `
                    <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>${event.contentItem?.text || '[No message content]'}</p>
                    </div>`;
        }

        function preRestartConversationHandler(event, instance) {
            console.log('pre:restartConversation event', event);
        }

        let calledRestartConversation = false;
        function restartConversationHandler(event, instance) {
            console.log('restartConversationHandler event', event);
            if (!calledRestartConversation) {
                setTimeout(() => {
                    instance.send('Hello from embedded web chat second time')
                }, 3000);
                calledRestartConversation = true;
            }

        }

        function preThreadLoadedHandler(event, instance) {
            console.log('pre:threadLoaded event', event);
            event.messages[0].content[0].text = 'Modified prompt in thread history'
        }

        async function authTokenNeededHandler(event, instance) {
            console.log('authTokenNeeded event', event);
            event.authToken = "<Refreshed Token>"
        }

        function onChatLoad(instance) {
            instance.on('chat:ready', (event, instance) => {
                console.log('chat:ready', event);
            });
            instance.once('pre:send', preSendHandler);
            instance.on('send', sendHandler);
            instance.once('pre:receive', preReceiveHandler);
            instance.on('receive', receiveHandler);
            instance.on('feedback', feedbackHandler);
            instance.on('userDefinedResponse', userDefinedResponseHandler);
            instance.on('pre:restartConversation', preRestartConversationHandler);
            instance.on('restartConversation', restartConversationHandler);
            instance.on('pre:threadLoaded', preThreadLoadedHandler);
            instance.on('authTokenNeeded', authTokenNeededHandler);
        }
        window.wxOConfiguration = {
            orchestrationID: '<tenantId>',
            hostUrl: 'http://localhost:3000',
            showLauncher: true,
            rootElementId: 'root',
            chatOptions: {
                agentId: '<agentId>',
                agentEnvironmentId: '<agentEnvironmentId>',
                onLoad: onChatLoad,
            },
        };
        setTimeout(function () {
            const script = document.createElement('script');
            script.src = `${window.wxOConfiguration.hostUrl}/wxoLoader.js?embed=true`;
            script.addEventListener('load', function () {
                wxoLoader.init();
            });
            document.head.appendChild(script);
        }, 0);
    </script>
```

## API Integration

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)
