Skip to main content
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.

Contents

Event categories

The following tables list all supported events, grouped by category.

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.

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.

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.

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.

Security events

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

Event details

The following sections provide detailed information about each event, including event properties, usage examples, and common patterns.

chat:ready

Triggered when the web chat is fully loaded and ready to receive user input. This is the ideal place to perform initialization tasks such as displaying a welcome message, configuring custom header items, or setting up other UI components. Event Properties
PropertyTypeDescription
typestringAlways 'chat:ready'.
Example
instance.on('chat:ready', (event, instance) => {
    console.log('Chat is ready for interaction');
    
    // Send a welcome message
    instance.send('Hello! How can I help you today?');
    
    // Initialize custom header items
    instance.updateCustomHeaderItems([
        {
            id: 'help',
            text: 'Help',
            type: 'menu-item',
            onClick: () => window.open('/help', '_blank')
        }
    ]);
});

pre:send

Triggered before the web chat sends a message to the agent. Use this event to inspect or modify the message content before it is dispatched to the backend. Event Properties
PropertyTypeDescription
typestringAlways 'pre:send'.
messageobjectThe message payload to send to the agent. Contains the user’s input and metadata.
message.messageobjectThe message object containing the content.
message.message.contentstringThe text content of the user’s message.
Example
instance.on('pre:send', (event, instance) => {
    console.log('About to send message:', event.message);
    
    // Convert message to uppercase before sending
    if (event?.message?.message?.content) {
        event.message.message.content = event.message.message.content.toUpperCase();
    }
    
    // Add custom metadata
    event.message.metadata = {
        timestamp: Date.now(),
        source: 'web-chat'
    };
});

send

Triggered after the web chat sends a message to the agent. This event is useful for analytics, logging, or triggering side effects after a message is dispatched. Event Properties
PropertyTypeDescription
typestringAlways 'send'.
messageobjectThe message payload that was sent to the agent.
Example
instance.on('send', (event, instance) => {
    console.log('Message sent:', event.message);
    
    // Log to analytics
    analytics.track('message_sent', {
        content: event.message.message.content,
        timestamp: Date.now()
    });
    
    // Show typing indicator
    showTypingIndicator();
});

pre:stream:delta

Triggered before each streaming delta chunk is processed and rendered in the chat. This event is fired during streaming responses when the agent sends partial content incrementally. Use this event to inspect, modify, or suppress streaming content before it appears in the chat UI. Event Properties
PropertyTypeDescription
typestringAlways 'pre:stream:delta'.
messageIdstringUnique identifier for the message being streamed.
threadIdstringUnique identifier for the conversation thread.
runIdstringUnique identifier for the current run.
deltaobjectThe streaming delta chunk containing partial response content.
delta.rolestringRole of the message sender, for example, 'assistant'.
delta.contentarrayArray of content items in the delta chunk.
delta.content[].idstringUnique identifier for the content item.
delta.content[].response_typestringType of the response item, for example, 'text'.
delta.content[].textstringText content of the delta item.
Example: Modify streaming content
instance.on('pre:stream:delta', (event, instance) => {
    console.log('Streaming delta received:', event);
    console.log('Message ID:', event.messageId);
    console.log('Thread ID:', event.threadId);
    
    // Modify streaming text before it's rendered
    event?.delta?.content?.forEach((contentItem) => {
        if (contentItem.response_type === 'text' && contentItem.text) {
            // Convert streaming text to uppercase
            contentItem.text = contentItem.text.toUpperCase();
            
            // Filter out specific words or phrases
            contentItem.text = contentItem.text.replace(/confidential/gi, '[REDACTED]');
        }
    });
    
    // Log streaming progress
    const deltaLength = event?.delta?.content?.[0]?.text?.length || 0;
    console.log(`Delta chunk received: ${deltaLength} characters`);
    
    // Track streaming metrics
    trackStreamingMetrics({
        messageId: event.messageId,
        threadId: event.threadId,
        runId: event.runId,
        deltaLength: deltaLength,
        timestamp: Date.now()
    });
});
Example: Suppress delta rendering
instance.on('pre:stream:delta', (event, instance) => {
    // Suppress delta rendering by emptying the content array
    // Streaming still occurs, but text won't appear character by character
    // Only the final complete message will be rendered
    event.delta.content = [];
    
    console.log('Delta rendering suppressed - final message will render after streaming completes');
});
Example: Translation pattern
let shouldSuppressDeltas = true;

instance.on('pre:stream:delta', (event, instance) => {
    // Suppress deltas during streaming
    if (shouldSuppressDeltas) {
        event.delta.content = [];
    }
});

instance.on('pre:receive', (event, instance) => {
    // Translate the complete message before rendering
    event?.message?.content?.forEach((contentItem) => {
        if (contentItem.response_type === 'text' && contentItem.text) {
            // Apply translation to complete message
            contentItem.text = translateText(contentItem.text);
        }
    });
});
Use Cases
  • Content filtering: Remove or redact sensitive information in real-time as it streams.
  • Text transformation: Apply formatting or transformations to streaming content.
  • Suppress streaming: Disable character-by-character streaming and show only the final message.
  • Translation workflows: Suppress deltas and translate the complete message in pre:receive.
  • Progress tracking: Monitor streaming progress and display custom loading indicators.
  • Analytics: Track streaming performance metrics like chunk size and frequency.
Notes
  • This event is only triggered when you use streaming responses. Non-streaming responses do not fire this event.
  • The delta.content array contains only the incremental content for the current chunk, not the complete message.
  • To suppress delta rendering, set event.delta.content = []. The complete message is still rendered with pre:receive and receive events.
  • The message object accumulates all previous deltas and represents the message state so far.
  • Multiple handlers can be registered and run in registration order.

pre:receive

Triggered before the web chat receives a response from the agent. Use this event to inspect or modify the agent’s response before it is rendered in the UI. Event Properties
PropertyTypeDescription
typestringAlways 'pre:receive'.
messageobjectThe agent’s response payload.
message.contentarrayArray of content items in the response.
message.content[].textstringText content of the response item.
message.content[].typestringType of the response item (e.g., 'text', 'user_defined').
Example
instance.on('pre:receive', (event, instance) => {
    console.log('About to receive message:', event.message);
    
    // Modify response text
    event?.message?.content?.forEach((element) => {
        if (element?.text?.includes('assistant')) {
            element.text = element.text.replace('assistant', 'Agent');
        }
    });
    
    // Add feedback controls to the last item
    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."
                }
            }
        };
    }
});

receive

Triggered after the web chat receives a response from the agent and the response is rendered in the UI. This event is useful for post-processing, analytics, or triggering follow-up actions. Event Properties
PropertyTypeDescription
typestringAlways 'receive'.
messageobjectThe agent’s response that was rendered.
Example
instance.on('receive', (event, instance) => {
    console.log('Message received:', event.message);
    
    // Hide typing indicator
    hideTypingIndicator();
    
    // Auto-scroll to latest message
    instance.doAutoScroll();
    
    // Log to analytics
    analytics.track('message_received', {
        messageId: event.message.id,
        timestamp: Date.now()
    });
});

pre:restartConversation

Triggered before the conversation restarts. This event is useful for alerting the user that the chat will reset, allowing them to complete any ongoing actions or save state before the restart occurs. Event Properties
PropertyTypeDescription
typestringAlways 'pre:restartConversation'.
Example
instance.on('pre:restartConversation', (event, instance) => {
    console.log('Conversation is about to restart');
    
    // Show confirmation dialog
    const confirmed = confirm('Are you sure you want to start a new conversation?');
    if (!confirmed) {
        // Note: You cannot prevent the restart, but you can perform cleanup
        return;
    }
    
    // Save current conversation state
    saveConversationState();
    
    // Clear any pending operations
    clearPendingOperations();
});

restartConversation

Triggered after the conversation restarts, before a new session begins. This event is useful for displaying specific UI elements, sending an initial message, or resetting application state when a new session starts. Event Properties
PropertyTypeDescription
typestringAlways 'restartConversation'.
Example
let hasRestarted = false;

instance.on('restartConversation', (event, instance) => {
    console.log('Conversation has restarted');
    
    // Send a welcome message after restart
    if (!hasRestarted) {
        setTimeout(() => {
            instance.send('Hello! I\'d like to start fresh.');
        }, 1000);
        hasRestarted = true;
    }
    
    // Reset application state
    resetAppState();
    
    // Show welcome banner
    showWelcomeBanner();
});

pre:threadLoaded

Triggered before a conversation thread is loaded from history. This event allows you to inspect or modify the messages before they are displayed in the chat UI. Event Properties
PropertyTypeDescription
typestringAlways 'pre:threadLoaded'.
messagesarrayArray of messages that will be loaded into the chat.
messages[].idstringUnique identifier for the message.
messages[].senderstringSender type ('user' or 'response').
messages[].contentarrayArray of content items in the message.
messages[].message_stateobjectState information for the message.
Example
instance.on('pre:threadLoaded', (event, instance) => {
    console.log('Loading thread with messages:', event.messages);
    
    // Add feedback controls to all agent responses
    event?.messages.forEach((message) => {
        if (message?.sender === 'response') {
            const [lastItem] = message.content;
            lastItem.message_options = {
                feedback: {
                    is_on: true
                }
            };
            
            // Initialize feedback state
            message.message_state = {
                content: {
                    1: {
                        feedback: {
                            text: "",
                            is_positive: true,
                            selected_categories: []
                        }
                    }
                }
            };
        }
    });
    
    // Modify first message
    if (event.messages.length > 0) {
        event.messages[0].content[0].text = 'Previous conversation loaded';
    }
});

view:pre:change

Triggered before the view state changes (e.g., showing or hiding the launcher, or main window). Use this event to react to or preprocess the upcoming view transition. Event Properties
PropertyTypeDescription
typestringAlways 'view:pre:change'.
reasonstringThe reason for the view change (e.g., 'launcherClicked', 'chatLoaded', 'calledChangeView', 'mainWindowMinimized').
oldViewStateobjectThe previous view state.
oldViewState.launcherbooleanWhether the launcher was visible.
oldViewState.mainWindowbooleanWhether the main window was visible.
newViewStateobjectThe new view state that will be applied. Can be modified by the handler.
newViewState.launcherbooleanWhether the launcher will be visible.
newViewState.mainWindowbooleanWhether the main window will be visible.
View Change Reasons
  • launcherClicked
    User clicked the launcher button.
  • chatLoaded
    Chat loaded for the first time.
  • calledChangeView
    View changed programmatically through the changeView() method.
  • mainWindowMinimized
    User clicked the minimize button.
Example
instance.on('view:pre:change', (event, instance) => {
    console.log('View about to change:', {
        reason: event.reason,
        from: event.oldViewState,
        to: event.newViewState
    });
    
    // Add CSS class during transition
    document.body.classList.add('chat-view-changing');
    
    // Modify the new view state if needed
    if (event.reason === 'launcherClicked') {
        // Ensure main window opens when launcher is clicked
        event.newViewState.mainWindow = true;
        event.newViewState.launcher = false;
    }
});

view:change

Triggered after the view state changes. This event enables post-change logic such as analytics, UI updates, or side effects. Event Properties
PropertyTypeDescription
typestringAlways 'view:change'.
reasonstringThe reason for the view change.
oldViewStateobjectThe previous view state.
oldViewState.launcherbooleanWhether the launcher was visible.
oldViewState.mainWindowbooleanWhether the main window was visible.
newViewStateobjectThe new view state.
newViewState.launcherbooleanWhether the launcher is now visible.
newViewState.mainWindowbooleanWhether the main window is now visible.
Example
instance.on('view:change', (event, instance) => {
    console.log('View changed:', {
        reason: event.reason,
        from: event.oldViewState,
        to: event.newViewState
    });
    
    // Remove transition CSS class
    document.body.classList.remove('chat-view-changing');
    
    // Track analytics
    if (event.newViewState.mainWindow && !event.oldViewState.mainWindow) {
        analytics.track('chat_opened', { reason: event.reason });
    }
    
    // Adjust page layout
    if (event.newViewState.mainWindow) {
        adjustPageLayout('chat-open');
    } else {
        adjustPageLayout('chat-closed');
    }
});

view:properties:pre:change

Triggered before one or more properties of a view are changed (e.g., maximized state). Use this event to react before the property change takes effect. Event Properties
PropertyTypeDescription
typestringAlways 'view:properties:pre:change'.
newViewStateobjectObject containing only the properties that are about to change.
newViewState.maximizedbooleanWhether the view will be maximized.
Example
instance.on('view:properties:pre:change', (event, instance) => {
    console.log('View properties about to change:', event.newViewState);
    
    if (event.newViewState.maximized) {
        console.log('Chat is about to be maximized');
        // Prepare UI for maximized state
        prepareMaximizedLayout();
    } else if (event.newViewState.maximized === false) {
        console.log('Chat is about to be restored');
        // Prepare UI for normal state
        prepareNormalLayout();
    }
});

view:properties:change

Triggered after one or more properties of a view have changed. This event enables post-change logic for property updates. Event Properties
PropertyTypeDescription
typestringAlways 'view:properties:change'.
newViewStateobjectObject containing only the properties that changed.
newViewState.maximizedbooleanWhether the view is now maximized.
Example
instance.on('view:properties:change', (event, instance) => {
    console.log('View properties changed:', event.newViewState);
    
    if (event.newViewState.maximized) {
        console.log('Chat is now maximized');
        analytics.track('chat_maximized');
    } else if (event.newViewState.maximized === false) {
        console.log('Chat is now restored to normal size');
        analytics.track('chat_restored');
    }
});

userDefinedResponse

Triggered when a response contains an unrecognized or user_defined response type. Use this event to render custom UI elements for specialized content types that are not natively supported by the chat widget. Event Properties
PropertyTypeDescription
typestringAlways 'userDefinedResponse'.
contentItemobjectThe individual message item with the user_defined response type.
contentItem.textstringText content of the item (if any).
messageobjectThe complete message containing the user-defined response item.
hostElementHTMLElementThe DOM element where you can insert custom content.
Example
instance.on('userDefinedResponse', (event, instance) => {
    console.log('User-defined response received:', event.contentItem);
    
    // Render custom HTML content
    event.hostElement.innerHTML = `
        <div class="custom-response">
            <div class="custom-header">
                <h3>Custom Content</h3>
            </div>
            <div class="custom-body">
                <p>${event.contentItem?.text || 'No content available'}</p>
            </div>
            <div class="custom-footer">
                <button onclick="handleCustomAction()">Take Action</button>
            </div>
        </div>
    `;
    
    // Or render a code snippet
    event.hostElement.innerHTML = `
        <cds-code-snippet>
            ${event.contentItem?.code || '// No code provided'}
        </cds-code-snippet>
    `;
});

customEvent

Triggered for custom events such as button clicks within the chat. This event provides a generic pattern for handling custom interactions. Event Properties
PropertyTypeDescription
typestringAlways 'customEvent'.
customEventNamestringThe name of the custom event (e.g., 'buttonItemClicked').
contentItemobjectThe message item that triggered the event.
messageobjectThe complete message containing the item.
Custom Event Names
  • buttonItemClicked
    Triggered when a button in a response is clicked.
Example
instance.on('customEvent', (event, instance) => {
    console.log('Custom event triggered:', event.customEventName);
    
    if (event.customEventName === 'buttonItemClicked') {
        console.log('Button clicked:', event.contentItem);
        
        // Handle button click
        const buttonId = event.contentItem.id;
        const buttonLabel = event.contentItem.label;
        
        // Perform action based on button
        handleButtonAction(buttonId, buttonLabel);
        
        // Track analytics
        analytics.track('button_clicked', {
            buttonId,
            buttonLabel,
            messageId: event.message.id
        });
    }
});

feedback

Triggered when the user interacts with feedback controls on a message. This includes both the feedback buttons (thumbs up and down) and the details popup where the user can submit additional information. Event Properties
PropertyTypeDescription
typestringAlways 'feedback'.
messageItemobjectThe message content item for which feedback was provided.
messageobjectThe complete message for which feedback was provided.
isPositivebooleanWhether the feedback is positive (true) or negative (false).
interactionTypestringThe type of interaction ('button' or 'details').
textstringThe text entered by the user (when submitting feedback details).
categoriesarrayArray of category strings selected by the user (when submitting feedback details).
Interaction Types
  • button
    User clicked thumbs up or down button.
  • details
    User submitted detailed feedback.
Example
instance.on('feedback', (event, instance) => {
    console.log('Feedback received:', {
        isPositive: event.isPositive,
        interactionType: event.interactionType,
        text: event.text,
        categories: event.categories
    });
    
    // Handle button click
    if (event.interactionType === 'button') {
        console.log(`User gave ${event.isPositive ? 'positive' : 'negative'} feedback`);
        
        // Show thank you message
        showNotification('Thank you for your feedback!');
    }
    
    // Handle detailed feedback submission
    if (event.interactionType === 'details') {
        console.log('Detailed feedback:', {
            text: event.text,
            categories: event.categories
        });
        
        // Send to analytics or backend
        submitFeedback({
            messageId: event.message.id,
            isPositive: event.isPositive,
            text: event.text,
            categories: event.categories,
            timestamp: Date.now()
        });
    }
});

authTokenNeeded

Triggered when the JWT authentication token expires and needs to be refreshed. Use this event to provide a new token to maintain secure communication with the backend. Event Properties
PropertyTypeDescription
typestringAlways 'authTokenNeeded'.
authTokenstring | nullThe expired token, or null if unavailable. You must set this property to the new token.
Example
instance.on('authTokenNeeded', async (event, instance) => {
    console.log('Auth token expired, refreshing...');
    
    try {
        // Fetch new token from your authentication service
        const response = await fetch('/api/auth/refresh', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ oldToken: event.authToken })
        });
        
        const data = await response.json();
        
        // Provide the new token
        event.authToken = data.newToken;
        
        console.log('Token refreshed successfully');
    } catch (error) {
        console.error('Failed to refresh token:', error);
        
        // Handle refresh failure (e.g., redirect to login)
        redirectToLogin();
    }
});

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);
        saveConversationState();
    }

    let hasRestarted = false;
    function restartConversationHandler(event, instance) {
        console.log('restartConversation event', event);
        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.