Use the embedded webchat to connect your agents to communication channels. A channel serves as an entry point to your agent, such as a website where you embed the agent, a Slack app, or other supported platforms.

Generating embedded webchat

To simplify integration with your website, the CLI includes the orchestrate channels webchat embed command. This command takes the name of an agent and produces a script tag that you can place in the <head></head> section of your page for the currently active environment.
BASH
orchestrate channels webchat embed --agent-name=test_agent1
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:
OUTPUT
<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://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
    },
  };

  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>

Customizing embedded webchat

Customizing 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:
ParameterTypeDescription
headerColorstringSet a six-digit hex code that defines the chat header color.
userMessageBackgroundColorstringSet a six-digit hex code that defines the user message bubble color.
primaryColorstringSet a six-digit hex code that defines the interactive elements color.
The following is an example of how to customize the embedded web chat using the style component inside window.wxOConfiguration:
JavaScript
<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"
  },
  
 style: {
   headerColor: '#000000',
   userMessageBackgroundColor: '#000000',
   primaryColor: '#000000'
  },
  
};

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>

Customizing layout

The watsonx Orchestrate embed supports a flexible layout object to control how and where the chat UI appears.
ParameterTypeDefaultDescription
rootElementIDstring(fullscreen-overlay only) ID of the container node to mount chat into.
showLauncherbooleantrue(fullscreen-overlay only) Show the bubble launcher (true) or render chat immediately (false).
layout.formstringfloatDefines the layout form of your web chat.

Use fullscreen-overlay to display the web chat in fullscreen mode. No additional parameters are required.

Use float to display the web chat as a floating window. Also configure:
  • width: Width of the web chat
  • height: Height of the web chat
Use custom to define a custom layout. Also configure the customElement parameter with your custom element.
layout.widthstring(float only) Popup width (e.g. ‘350px’, ‘30rem’).
layout.heightstring(float only) Popup height (e.g. ‘500px’, ‘40rem’).
layout.showOrchestrateHeaderbooleantrueRender the standard header bar (true) or hide it(false).
layout.customElementHTMLElementelement reference to render into.
[JavaScript]
<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:
[JavaScript]
<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>

Enabling thumbs-up and thumbs-down

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]
<script>
function feedbackHandler(event) {
    console.log('feedback', event);
}

function preReceiveHandler(event) {
    console.log('pre-receive event', event);


    const lastItem = event?.content?.[event.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>

Enabling secure embed flow

To enable security for the Embed experience, follow the steps below using the Embed Settings API:
1

(Optional): Get Embed Config

curl -X GET 'http://localhost:4321/api/v1/private/embed/config' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer <WXO_JWT>'
2

Generate IBM Public Key

curl -X POST 'http://localhost:4321/api/v1/private/embed/generate-key-pair' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer <WXO_JWT>' \
  -d ''
3

Generate RS256 Key Pair

For more information, see Generating an RS256 key pair.If you are only developing, you can use an RSA Key generator, such as CryptoTools.This will generate the Client Public Key and Client Private Key.
4

Set Embed Config

curl -X POST 'http://localhost:4321/api/v1/private/embed/config' \
  -H 'accept: application/json' \
  -H 'Authorization: Bearer <WXO_JWT>' \
  -H 'Content-Type: application/json' \
  -d '{
    "public_key": "<IBM Public Key from Step 2>",
    "client_public_key": "<Client Public Key from Step 3>",
    "is_security_enabled": true
}'

Events reference

Embedded webchat 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 nameDescription
userDefinedResponseTriggered when a response contains an unrecognized or user_defined response type.

Message Events

Event nameDescription
pre:sendTriggered before the webchat sends a message to the assistant.
sendTriggered after the webchat sends a message to the assistant.
pre:receiveTriggered before the webchat receives a response from the assistant.
receiveTriggered after the webchat receives a response from the assistant.
pre:restartConversationTriggered 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).
restartConversationTriggered after the conversation restarts, before a new session begins. Useful for displaying specific UI elements when a new session starts.

View Events

Event nameDescription
view:pre:changeTriggered before the view state changes.
view:changeTriggered after the view state changes.
pre:threadLoadedTriggered when a user navigates to a chat thread in full-screen embedded chat. Useful for displaying custom responses or UI elements.

Security Events

Event nameDescription
identityTokenExpiredTriggered when security is enabled and the JWT token expires.
authTokenNeededTriggered when the embedded chat requires a refreshed or updated authentication token.

Miscellaneous Events

Event nameDescription
chat:readyTriggered when the webchat is fully loaded and ready to receive user input. Useful for displaying a welcome message or initializing UI components.

Events example

The following example shows how to configure events in the embedded webchat:
[JavaScript]
    <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 webchat 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 = {
            clientVersion: 'latest',
            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>