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 channels | Widget mode |
|---|---|
| Web voice (+ telephony) | voice — orb + Start/Mute/Hangup |
| Web chat | chat — message list + input |
| Both | both — 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:
| Attribute | Values | Description |
|---|---|---|
data-voisnap-agent | (required) | Your agent key |
data-voisnap-trigger | floating | programmatic | both | floating (default): corner button. programmatic: no button, open via Voisnap.open(). both: button and the JS API |
data-voisnap-color | #RRGGBB | Button background + accent color |
data-voisnap-position | bottom-right | bottom-left | Where the button anchors |
data-voisnap-icon | URL or data: URL | Custom button icon (PNG/WebP/SVG) |
data-voisnap-auto-open | true | false | Open 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:
| Attribute | Example | Affects |
|---|---|---|
data-voisnap-radius | 16px | Chat window corner radius |
data-voisnap-font | Inter, sans-serif | Widget chrome font (default: inherit) |
data-voisnap-button-size | 72px | Floating button diameter |
data-voisnap-window-width | 360px | Docked chat window width |
data-voisnap-shadow | 0 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 }
}
}
theme—accent,radius,font,buttonSize,windowWidth,shadow.labels— override any built-in string (startConvo,send,typeMessage,endConvo,connecting, …). Overrides win in every language.display—showAgentName,showStatusBar,compactMode.behavior—allowInput(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; preferlogout().
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.Voisnapbefore callingupdateTokens.
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;