Skip to main content

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.

Embedded web chat supports a variety of events that allow you to trigger specific actions or customize behavior at different stages of the chat lifecycle. Events enable you to intercept, modify, or respond to user interactions and system state changes in real time. The following tables list all supported events, grouped by category.

Conversation events

Conversation events track conversation lifecycle operations such as restarts and thread loading.
Event nameDescription
pre:restartConversationTriggered before the conversation restarts.
restartConversationTriggered after the conversation restarts, before a new session begins.
pre:threadLoadedTriggered before a conversation thread is loaded from history.

Customization events

Customization events enable you to handle custom response types and render custom UI elements.
Event nameDescription
userDefinedResponseTriggered when a response contains an unrecognized or user_defined response type.
customEventTriggered for custom events like button clicks.

Feedback events

Feedback events track user interactions with feedback controls on messages. For a complete guide on implementing thumbs-up and thumbs-down feedback, see Thumbs-up and thumbs-down feedback.
Event nameDescription
feedbackTriggered when the user interacts with feedback controls on a message.

Lifecycle events

Lifecycle events track the initialization and readiness state of the chat widget.
Event nameDescription
chat:readyTriggered when the web chat is fully loaded and ready to receive user input.

Message events

Message events allow you to intercept and modify messages as they flow between the user and the agent.
Event nameDescription
pre:sendTriggered before the web chat sends a message to the agent.
sendTriggered after the web chat sends a message to the agent.
pre:stream:deltaTriggered before each streaming delta chunk is processed and rendered in the chat.
pre:receiveTriggered before the web chat receives a response from the agent.
receiveTriggered after the web chat receives a response from the agent.

Security events

Security events help manage authentication and token lifecycle.
Event nameDescription
authTokenNeededTriggered when the JWT authentication token expires and needs to be refreshed.

View events

View events track changes to the chat widget’s visual state and layout.
Event nameDescription
view:pre:changeTriggered before the view state changes.
view:changeTriggered after the view state changes.
view:properties:pre:changeTriggered before view properties (like maximized state) change.
view:properties:changeTriggered after view properties (like maximized state) change.

Complete example

The following example demonstrates how to configure multiple events in the embedded web chat:
<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);
        
        if (event.interactionType === 'details') {
            // Submit feedback to backend
            // Note: watsonx Orchestrate does not persist feedback internally
            fetch('/api/store-feedback', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'Authorization': `Bearer ${jwtToken}`
                },
                body: JSON.stringify({
                    messageId: event.message.id,
                    isPositive: event.isPositive,
                    text: event.text,
                    categories: event.categories
                })
            });
        }
    }

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

        // Add feedback controls
        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: ['Helpful', 'Accurate', 'Clear'],
                        disclaimer: "Your feedback helps us improve.",
                    },
                    negative_options: {
                        categories: ['Inaccurate', 'Incomplete', 'Confusing', 'Other'],
                        disclaimer: "Please provide details to help us improve.",
                    },
                },
            };
        }
    }

    function receiveHandler(event, instance) {
        console.log('received event', event);
        instance.off('pre:receive', preReceiveHandler);
    }

    function userDefinedResponseHandler(event, instance) {
        console.log('userDefinedResponse event', event);
        event.hostElement.innerHTML = `
            <div style="background-color:#f4f4f4;padding:15px;border-radius:8px;">
                <h4>Custom Response</h4>
                <p>${event.contentItem?.text || '[No content]'}</p>
            </div>`;
    }

    function preStreamDeltaHandler(event, instance) {
        console.log('pre:stream:delta event', event);
        
        // Filter sensitive content in streaming responses
        event?.delta?.content?.forEach((contentItem) => {
            if (contentItem.response_type === 'text' && contentItem.text) {
                contentItem.text = contentItem.text.replace(/confidential/gi, '[REDACTED]');
            }
        });
        
        // Track streaming metrics
        const deltaLength = event?.delta?.content?.[0]?.text?.length || 0;
        console.log('Delta chunk size:', deltaLength);
    }

    function preRestartConversationHandler(event, instance) {
        console.log('pre:restartConversation event', event);
        console.log('Restart source:', event.interactionType);
        
        // Handle different restart sources
        if (event.interactionType === 'restart_button') {
            console.log('User clicked the Restart button');
        } else if (event.interactionType === 'new_chat_button') {
            console.log('User clicked the New Chat button');
        } else if (event.interactionType === 'instance_method') {
            console.log('Restart triggered programmatically');
        }
    }

    let hasRestarted = false;
    function restartConversationHandler(event, instance) {
        console.log('restartConversation event', event);
        console.log('Restart source:', event.interactionType);
        
        if (!hasRestarted) {
            setTimeout(() => {
                instance.send('Hello! Starting a new conversation.');
            }, 1000);
            hasRestarted = true;
        }
    }

    function preThreadLoadedHandler(event, instance) {
        console.log('pre:threadLoaded event', event);
        
        // Add feedback to historical messages
        event?.messages.forEach((message) => {
            if (message?.sender === 'response') {
                const [lastItem] = message.content;
                lastItem.message_options = {
                    feedback: { is_on: true }
                };
                message.message_state = {
                    content: {
                        1: {
                            feedback: {
                                text: "",
                                is_positive: true,
                                selected_categories: []
                            }
                        }
                    }
                };
            }
        });
    }

    async function authTokenNeededHandler(event, instance) {
        console.log('authTokenNeeded event', event);
        
        // Fetch refreshed token
        const newToken = await fetchRefreshedToken();
        event.authToken = newToken;
    }

    async function viewPreChange(event, instance) {
        console.log('view:pre:change event', event);
        document.body.classList.add('chat-view-changing');
    }

    async function viewChange(event, instance) {
        console.log('view:change event', event);
        document.body.classList.remove('chat-view-changing');
        
        if (event.newViewState.mainWindow) {
            analytics.track('chat_opened', { reason: event.reason });
        }
    }

    function onChatLoad(instance) {
        // Lifecycle events
        instance.on('chat:ready', (event, instance) => {
            console.log('chat:ready', event);
        });

        // Message events
        instance.once('pre:send', preSendHandler);
        instance.on('send', sendHandler);
        instance.on('pre:stream:delta', preStreamDeltaHandler);
        instance.once('pre:receive', preReceiveHandler);
        instance.on('receive', receiveHandler);

        // Conversation events
        instance.on('pre:restartConversation', preRestartConversationHandler);
        instance.on('restartConversation', restartConversationHandler);
        instance.on('pre:threadLoaded', preThreadLoadedHandler);

        // Customization events
        instance.on('feedback', feedbackHandler);
        instance.on('userDefinedResponse', userDefinedResponseHandler);

        // Security events
        instance.on('authTokenNeeded', authTokenNeededHandler);

        // View events
        instance.on('view:pre:change', viewPreChange);
        instance.on('view:change', viewChange);
    }

    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>

Best practices

Event handler patterns

  1. Use once() for one-time operations
    If you only need to handle an event once, use instance.once() instead of instance.on() to automatically remove the handler after execution.
  2. Clean up event handlers
    Use instance.off() to remove event handlers when they’re no longer needed to prevent memory leaks.
  3. Handle errors gracefully
    Wrap event handler logic in try-catch blocks to prevent errors from breaking the chat experience.
  4. Avoid blocking operations
    Keep event handlers lightweight and non-blocking. Use async operations for heavy processing.

Modifying event data

  • pre:send Modify event.message.message.content to change the user’s message before sending.
  • pre:receive Modify event.message.content to change the agent’s response before rendering.
  • pre:stream:delta Modify event.delta.text to transform streaming content in real-time before it’s rendered.
  • pre:threadLoaded Modify event.messages to customize historical messages before display.
  • view:pre:change Modify event.newViewState to control the upcoming view state.

Common use cases

  • Analytics tracking Use send, receive, and view:change events to track user interactions.
  • Custom rendering Use userDefinedResponse to render custom UI for specialized content types.
  • Token management Use authTokenNeeded to implement automatic token refresh.
  • Message transformation Use pre:send and pre:receive to transform messages in transit.
  • Streaming content processing Use pre:stream:delta to filter, transform, or monitor streaming responses in real-time.
  • Feedback collection Use feedback event to capture and process user feedback.