Virbe Documentation

Integration Guide

Step-by-step patterns for integrating the Virbe web widget into vanilla JavaScript websites and React applications.

This guide covers how to load the widget script, embed it in your page, and control it programmatically from both vanilla JavaScript and React applications.

Loading the Script

The widget is distributed as a JavaScript module. Load it before using the <virbe-plugin> element.

<script type="module" src="https://YOUR_DASHBOARD_URL/web-component/virbe-plugin.es.js"></script>

Replace YOUR_DASHBOARD_URL with your Virbe dashboard domain (e.g. https://your-dashboard-name.virbe.app). The script registers the <virbe-plugin> custom element globally. This is the same script tag that the Profile's Deployment tab includes in the copy-paste embed code – see Web Widget Deployment.

Dynamic Script Loading

If you need to load the widget conditionally or at a specific point in your application lifecycle, load it dynamically:

function loadVirbeWidget(dashboardUrl) {
  return new Promise((resolve, reject) => {
    const script = document.createElement('script');
    script.type = 'module';
    script.async = true;
    script.src = `${dashboardUrl}/web-component/virbe-plugin.es.js`;
    script.onload = resolve;
    script.onerror = reject;
    document.body.appendChild(script);
  });
}

// Usage
await loadVirbeWidget('https://YOUR_DASHBOARD_URL');

Vanilla HTML Integration

A complete, copy-pasteable example:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title>Virbe Widget Demo</title>
</head>
<body>
  <!-- 1. Place the widget -->
  <virbe-plugin
    profileId="YOUR_PROFILE_ID"
    profileSecret="YOUR_PROFILE_SECRET"
    mode="floating"
    position="bottom">
  </virbe-plugin>

  <!-- 2. Load the script -->
  <script type="module" src="https://YOUR_DASHBOARD_URL/web-component/virbe-plugin.es.js"></script>

  <!-- 3. Interact with the widget -->
  <script type="module">
    // Wait for the custom element to be defined
    await customElements.whenDefined('virbe-plugin');

    const widget = document.querySelector('virbe-plugin');

    // Listen for initialization
    widget.subscribeToEvent('onInitialized', () => {
      console.log('Widget ready');
    });

    // Listen for messages
    widget.subscribeToEvent('onConversationMessage', (event) => {
      console.log('Message:', event.detail.action);
    });
  </script>
</body>
</html>

Programmatic Element Creation

You can also create the widget entirely in JavaScript:

const widget = document.createElement('virbe-plugin');
widget.profileId = 'YOUR_PROFILE_ID';
widget.profileSecret = 'YOUR_PROFILE_SECRET';
widget.mode = 'floating';
widget.position = 'bottom';
document.body.appendChild(widget);

React Integration

Script Loading Hook

Create a hook to load the widget script dynamically:

import { useState, useEffect } from 'react';

function useLoadScript(src: string): boolean {
  const [loaded, setLoaded] = useState(false);

  useEffect(() => {
    if (!src) return;

    const existing = document.querySelector(`script[src="${src}"]`);
    if (existing) {
      setLoaded(true);
      return;
    }

    const script = document.createElement('script');
    script.type = 'module';
    script.async = true;
    script.src = src;
    script.onload = () => setLoaded(true);
    document.body.appendChild(script);
  }, [src]);

  return loaded;
}

Wrapper Component

Wrap the <virbe-plugin> custom element in a React component to expose its methods via a ref:

import { useRef, useEffect, useImperativeHandle, forwardRef } from 'react';

interface VirbePluginMethods {
  showWidget(): void;
  hideWidget(): void;
  startNewConversation(endUserId: string): void;
  startConversation(conversationId: string, endUserId?: string): void;
  stopConversation(): void;
  sendText(message: string, language?: string): void;
  sendSignal(signal: string, value?: string): void;
  mute(): void;
  unmute(): void;
  startSpeechRecord(): void;
  stopSpeechRecord(): void;
  interruptSpeech(): void;
  subscribe(event: string, callback: (event: CustomEvent) => void): () => void;
}

interface VirbePluginWrapperProps {
  profileId: string;
  profileSecret: string;
  dashboardUrl: string;
  mode?: 'floating' | 'embedded';
  position?: 'top' | 'bottom';
}

const VirbePluginWrapper = forwardRef<VirbePluginMethods, VirbePluginWrapperProps>(
  ({ profileId, profileSecret, dashboardUrl, mode = 'floating', position = 'bottom' }, ref) => {
    const elementRef = useRef<HTMLElement>(null);
    const isScriptLoaded = useLoadScript(`${dashboardUrl}/web-component/virbe-plugin.es.js`);

    useImperativeHandle(ref, () => ({
      showWidget: () => (elementRef.current as any)?.showWidget(),
      hideWidget: () => (elementRef.current as any)?.hideWidget(),
      startNewConversation: (endUserId) =>
        (elementRef.current as any)?.startNewConversation(endUserId),
      startConversation: (conversationId, endUserId) =>
        (elementRef.current as any)?.startConversation(conversationId, endUserId),
      stopConversation: () => (elementRef.current as any)?.stopConversation(),
      sendText: (message, language) =>
        (elementRef.current as any)?.sendText(message, language),
      sendSignal: (signal, value) =>
        (elementRef.current as any)?.sendSignal(signal, value),
      mute: () => (elementRef.current as any)?.mute(),
      unmute: () => (elementRef.current as any)?.unmute(),
      startSpeechRecord: () => (elementRef.current as any)?.startSpeechRecord(),
      stopSpeechRecord: () => (elementRef.current as any)?.stopSpeechRecord(),
      interruptSpeech: () => (elementRef.current as any)?.interruptSpeech(),
      subscribe: (event, callback) => {
        const sub = (elementRef.current as any)?.subscribeToEvent(event, callback);
        return () => sub?.unsubscribe();
      },
    }));

    if (!isScriptLoaded) return null;

    return (
      // @ts-expect-error virbe-plugin is a custom element
      <virbe-plugin
        ref={elementRef}
        profileId={profileId}
        profileSecret={profileSecret}
        mode={mode}
        position={position}
      />
    );
  }
);

Using the Wrapper

import { useRef, useEffect } from 'react';

function App() {
  const pluginRef = useRef<VirbePluginMethods>(null);

  useEffect(() => {
    if (!pluginRef.current) return;

    const unsubInit = pluginRef.current.subscribe('onInitialized', () => {
      console.log('Widget ready');
    });

    const unsubStatus = pluginRef.current.subscribe('onApiStatusChanged', (event) => {
      if (event.detail.action === 'connected') {
        pluginRef.current?.unmute();
        pluginRef.current?.startNewConversation('user-123');
      }
    });

    const unsubMessages = pluginRef.current.subscribe('onConversationMessage', (event) => {
      console.log('Message:', event.detail.action);
    });

    return () => {
      unsubInit();
      unsubStatus();
      unsubMessages();
    };
  }, []);

  return (
    <VirbePluginWrapper
      ref={pluginRef}
      profileId="YOUR_PROFILE_ID"
      profileSecret="YOUR_PROFILE_SECRET"
      dashboardUrl="https://YOUR_DASHBOARD_URL"
    />
  );
}

Controlling the Widget from Your Application

Show or Hide Based on User Actions

// Open widget when user clicks a help button
document.getElementById('help-button').addEventListener('click', () => {
  widget.showWidget();
});

// Hide widget when navigating to a checkout page
router.on('/checkout', () => {
  widget.hideWidget();
});

Start Conversations Programmatically

// Start a new conversation with context variables
widget.startNewConversation('user-123', [
  { key: 'page', value: window.location.pathname },
  { key: 'language', value: navigator.language },
]);

Send Signals from External UI

Use signals to trigger specific conversation flows configured in the dashboard:

// A button in your app triggers a signal
document.getElementById('product-question').addEventListener('click', () => {
  widget.sendSignal('ask-about-product', 'premium-plan');
});

Route Events to Your Own UI

Listen to widget events and display data in your own components:

widget.subscribeToEvent('onConversationMessage', (event) => {
  const message = event.detail.action;
  // Render message in your custom chat panel
  appendToChatLog(message);
});

widget.subscribeToEvent('onUiAction', (event) => {
  const { cards, buttons } = event.detail.action;
  // Render action buttons in your own UI
  renderActionButtons(buttons);
});

When controlling conversations programmatically, disable automatic conversation start in your Profile's dashboard settings:

  • Set Auto-start on widget focused to false
  • Set Auto-start on page focused to false
  • Set Send signal on new conversation to false

Otherwise the widget may start conversations before your code is ready to manage them.

On this page