Voisnap Docs
Guides

Web Widget

Embed a Voisnap voice or chat widget on any website with a single script tag — loader attributes, theming, ui_config, the JavaScript API, pass-through auth, and building a fully custom UI.

Web Widget

The Voisnap web widget embeds a voice or chat interface on any website with a single <script> tag. Visitors talk to or chat with your agent directly, without leaving your site.

The widget loads a small loader from your API region, fetches the agent's public config, and renders a floating button plus a chat/voice window.


Modes

The widget's mode is derived from the agent's enabled channels — you don't set it on the embed tag:

Enabled channelsWidget mode
Web voice (+ telephony)voice — orb + Start/Mute/Hangup
Web chatchat — message list + input
Bothboth — chat input and a voice Start button

You can still force a mode for a single session with Voisnap.open({ mode: 'voice' }).


Quick embed

Add this to your page — replace your-agent-key with your agent's key (use your region's API host; api.voisnap.ai resolves to the default region):

<script
  src="https://api.voisnap.ai/api/v1/widget/loader.js"
  data-voisnap-agent="your-agent-key"
  data-voisnap-color="#4F46E5"
  data-voisnap-position="bottom-right"
  async
></script>

That's it — a floating button appears in the corner. The agent must have the embedding domain in its allowed-domains list, or the loader logs a blocked-origin message.


Loader attributes

All configuration is via data-voisnap-* attributes on the <script> tag:

AttributeValuesDescription
data-voisnap-agent(required)Your agent key
data-voisnap-triggerfloating | programmatic | bothfloating (default): corner button. programmatic: no button, open via Voisnap.open(). both: button and the JS API
data-voisnap-color#RRGGBBButton background + accent color
data-voisnap-positionbottom-right | bottom-leftWhere the button anchors
data-voisnap-iconURL or data: URLCustom button icon (PNG/WebP/SVG)
data-voisnap-auto-opentrue | falseOpen centered as soon as the script loads

Theming tokens (Tier 1)

One-line appearance overrides — each maps to a CSS variable with a safe default:

AttributeExampleAffects
data-voisnap-radius16pxChat window corner radius
data-voisnap-fontInter, sans-serifWidget chrome font (default: inherit)
data-voisnap-button-size72pxFloating button diameter
data-voisnap-window-width360pxDocked chat window width
data-voisnap-shadow0 10px 40px rgba(0,0,0,.25)Window drop shadow
<script
  src="https://api.voisnap.ai/api/v1/widget/loader.js"
  data-voisnap-agent="your-agent-key"
  data-voisnap-color="#0ea5e9"
  data-voisnap-radius="16px"
  data-voisnap-font="Inter, system-ui, sans-serif"
  data-voisnap-button-size="72px"
  async
></script>

JavaScript API

When the loader runs with data-voisnap-trigger="programmatic" or both, these methods are available on window.Voisnap:

Voisnap.open(opts?);      // open docked in the corner
Voisnap.openDemo(opts?);  // open centered (modal-style)
Voisnap.close();
Voisnap.isOpen();         // → boolean
 
Voisnap.updateTokens(accessToken, refreshToken, expiresAtUtc);  // pass-through auth
Voisnap.clearTokens();
Voisnap.logout();         // end session + clear conversation, start fresh anonymous

open() / openDemo() accept:

Voisnap.open({
  mode: 'voice',            // 'voice' | 'chat' | 'both' (override for this session)
  userName: 'Alex',         // caller context shown to the agent
  language: 'Hebrew',       // English language name — agent responds in this language
  autoStart: true,          // voice: connect immediately instead of waiting for Start
  accentColor: '#4F46E5',   // overrides data-voisnap-color for this session
  theme: {                  // Tier-1 tokens at runtime
    radius: '16px', font: 'Inter, sans-serif',
    buttonSize: '72px', windowWidth: '360px', shadow: '0 10px 40px rgba(0,0,0,.25)',
  },
  onClose: () => console.log('Widget closed'),
});

Labels & behavior — ui_config (Tier 2)

For deeper per-agent customization without forking, set the agent's ui_config to a JSON object with a widget key. The loader serves it via config.json and the widget applies it with fallback to the built-in defaults (so anything you omit is unchanged):

{
  "widget": {
    "theme":    { "accent": "#0ea5e9", "radius": "16px", "font": "Inter, sans-serif" },
    "labels":   { "startConvo": "Start a call", "send": "Send", "typeMessage": "Ask us anything…" },
    "display":  { "showAgentName": true, "showStatusBar": true, "compactMode": false },
    "behavior": { "allowInput": true, "autoCloseOnEnd": false }
  }
}
  • themeaccent, radius, font, buttonSize, windowWidth, shadow.
  • labels — override any built-in string (startConvo, send, typeMessage, endConvo, connecting, …). Overrides win in every language.
  • displayshowAgentName, showStatusBar, compactMode.
  • behaviorallowInput (show the chat input), autoCloseOnEnd (collapse when the call ends).

You can edit these from the console's Widget API reference → Theme & UI tab, or via the agents API (PUT /agents/{id} with uiConfig).


Authentication: login & logout

The embedded widget starts every conversation anonymously. To let the agent act on the signed-in user's account (orders, plans, shipments, etc.), pass the end-user's access token to the widget from your site's auth handlers using the window.Voisnap API.

The contract is explicit: the widget does not read your cookies or storage. Call these on login and logout so the widget always reflects the current auth state.

// On login (and on page load if the user is already signed in):
// continues the SAME conversation, now authenticated — nothing is lost.
window.Voisnap.updateTokens(
  accessToken,        // the end-user's OAuth/JWT access token
  refreshToken,       // optional
  expiresAtUtc        // Date | ISO string | epoch ms — REQUIRED, else the token is ignored
);
 
// Optional: refresh the token shortly before it expires.
window.Voisnap.open({
  token: accessToken,
  tokenExpiresAt: expiresAtUtc,
  onTokenExpiring: async () => {
    const next = await myRefresh();
    return { accessToken: next.token, expiresAtUtc: next.expiresAt };
  },
});
 
// On logout: ENDS the server session, clears the stored conversation + transcript,
// and starts a fresh anonymous session. Use this (not clearTokens) at logout.
window.Voisnap.logout();

Behavior notes:

  • Login mid-chat continues the conversation — the agent gains account access on the next message (the system prompt re-renders for the authenticated user).
  • Logout deletes the conversation — history is tied to the session and cannot be resumed.
  • Hard refresh resumes the conversation, unless the last activity was more than 30 minutes ago (matching the server's idle timeout). Logout in another tab also clears this tab.
  • clearTokens() only drops the credential without ending the session; prefer logout().

Tokens are held only in the runtime session memory and are never persisted server-side.


React integration

Render the loader once and forward auth on login. The widget renders itself into document.body:

import { useEffect } from 'react';
import { useAuth } from './hooks/useAuth';
 
const LOADER = 'https://api.voisnap.ai/api/v1/widget/loader.js';
 
export function VoisnapWidget({ agentKey, color = '#4F46E5' }: { agentKey: string; color?: string }) {
  const { user, accessToken, expiresAt } = useAuth();
 
  useEffect(() => {
    const s = document.createElement('script');
    s.src = LOADER;
    s.async = true;
    s.setAttribute('data-voisnap-agent', agentKey);
    s.setAttribute('data-voisnap-color', color);
    s.setAttribute('data-voisnap-trigger', 'both'); // button + JS API
    document.body.appendChild(s);
    return () => { s.remove(); };
  }, [agentKey, color]);
 
  // Forward / clear auth as the user signs in and out.
  useEffect(() => {
    if (!window.Voisnap) return;
    if (accessToken) window.Voisnap.updateTokens(accessToken, null, expiresAt);
    else window.Voisnap.logout();
  }, [accessToken, expiresAt, user]);
 
  return null;
}

If the loader is still initializing when your auth effect runs, guard with a small readiness poll for window.Voisnap before calling updateTokens.


Fully custom UI (Tier 3)

When theming and ui_config aren't enough and you want to own the entire interface, build directly on the realtime SignalR hubs with the @voisnap/widget-core package — typed hub contracts, Web-Audio PCM helpers, and reference clients. See Build your own widget.


Content Security Policy

If your site uses a CSP, allow the API host (use your region's host):

connect-src https://api.voisnap.ai wss://api.voisnap.ai;
script-src  https://api.voisnap.ai;

On this page