Widget Integration: Bringing Your Agent to Life
The VOX widget is the customer-facing entry point for your voice agent. It's a lightweight JavaScript component that embeds seamlessly into your website, handles microphone permissions, streams audio to the VOX platform, and renders UI components returned by your agent's tools.
This guide covers everything you need to embed, customize, and troubleshoot the widget across different environments and frameworks.
Quick Start: Basic Embedding
The simplest way to embed the VOX widget is to add a script block to your HTML:
<!-- Add this snippet before the closing </body> tag -->
<script>
(function() {
window.voxConfig = {
tenantId: 'your-tenant-id',
agentId: 'your-agent-id',
widgetKey: 'w_your_widget_key_here'
};
var script = document.createElement('script');
script.src = 'https://vox.strategicmachines.ai/widget.js';
script.async = true;
document.head.appendChild(script);
})();
</script>
Required Configuration:
tenantId— Your unique tenant identifier (e.g.,'cypress-resorts')agentId— The specific agent to load (e.g.,'concierge')widgetKey— The widget key tied to your domain origin
What Happens Next:
- Widget loads asynchronously (no blocking)
- A voice button appears on your page (bottom-right by default)
- When clicked, the widget requests microphone permissions
- User speaks → audio streams to VOX → agent responds → UI renders
Configuration Options
Customize the widget's appearance, behavior, and positioning:
<script>
window.voxConfig = {
// Required
tenantId: 'your-tenant-id',
agentId: 'your-agent-id',
widgetKey: 'w_your_widget_key_here',
// Optional Customization
position: 'bottom-right', // 'bottom-left', 'top-right', 'top-left'
theme: 'light', // 'light', 'dark', 'auto'
primaryColor: '#0066CC', // Hex color for widget accent
buttonSize: 'medium', // 'small', 'medium', 'large'
autoOpen: false, // Auto-start conversation on page load
greeting: 'Hi! How can I help you today?', // Initial message
zIndex: 9999, // CSS z-index for widget overlay
// Advanced Options
sessionMetadata: {
page: window.location.pathname,
referrer: document.referrer,
customerId: getUserId() // Your function to get user context
}
};
</script>
Configuration Reference
| Option | Type | Default | Description |
|---|---|---|---|
tenantId | string | required | Your tenant identifier |
agentId | string | required | Which agent to load |
widgetKey | string | required | Widget authentication key |
position | string | 'bottom-right' | Widget button placement |
theme | string | 'light' | Color theme |
primaryColor | string | '#0066CC' | Accent color for buttons and highlights |
buttonSize | string | 'medium' | Size of the voice button |
autoOpen | boolean | false | Start conversation immediately on load |
greeting | string | Agent-defined | Override the agent's default greeting |
zIndex | number | 9999 | CSS stacking order |
sessionMetadata | object | {} | Custom data passed to agent session |
Framework-Specific Integration
React
Create a dedicated widget component:
// components/VoxWidget.jsx
import { useEffect } from 'react';
export default function VoxWidget({ tenantId, agentId, widgetKey }) {
useEffect(() => {
// Configure the widget
window.voxConfig = {
tenantId,
agentId,
widgetKey,
position: 'bottom-right',
theme: 'auto'
};
// Load the widget script
const script = document.createElement('script');
script.src = 'https://vox.strategicmachines.ai/widget.js';
script.async = true;
document.head.appendChild(script);
// Cleanup on unmount
return () => {
const existingScript = document.querySelector('script[src*="vox.strategicmachines.ai"]');
if (existingScript) {
existingScript.remove();
}
// Cleanup widget instance
if (window.voxWidget) {
window.voxWidget.destroy();
}
};
}, [tenantId, agentId, widgetKey]);
return null; // Widget renders itself
}
Usage:
// app/layout.jsx or pages/_app.jsx
import VoxWidget from '@/components/VoxWidget';
export default function RootLayout({ children }) {
return (
<html>
<body>
{children}
<VoxWidget
tenantId="your-tenant-id"
agentId="your-agent-id"
widgetKey={process.env.NEXT_PUBLIC_VOX_WIDGET_KEY}
/>
</body>
</html>
);
}
Vue
<!-- components/VoxWidget.vue -->
<template>
<div></div> <!-- Widget renders itself via script -->
</template>
<script>
export default {
name: 'VoxWidget',
props: {
tenantId: String,
agentId: String,
widgetKey: String
},
mounted() {
window.voxConfig = {
tenantId: this.tenantId,
agentId: this.agentId,
widgetKey: this.widgetKey,
position: 'bottom-right'
};
const script = document.createElement('script');
script.src = 'https://vox.strategicmachines.ai/widget.js';
script.async = true;
document.head.appendChild(script);
},
beforeUnmount() {
const script = document.querySelector('script[src*="vox.strategicmachines.ai"]');
if (script) script.remove();
if (window.voxWidget) window.voxWidget.destroy();
}
};
</script>
Angular
// app/components/vox-widget.component.ts
import { Component, OnInit, OnDestroy, Input } from '@angular/core';
@Component({
selector: 'app-vox-widget',
template: ''
})
export class VoxWidgetComponent implements OnInit, OnDestroy {
@Input() tenantId!: string;
@Input() agentId!: string;
@Input() widgetKey!: string;
ngOnInit() {
(window as any).voxConfig = {
tenantId: this.tenantId,
agentId: this.agentId,
widgetKey: this.widgetKey
};
const script = document.createElement('script');
script.src = 'https://vox.strategicmachines.ai/widget.js';
script.async = true;
document.head.appendChild(script);
}
ngOnDestroy() {
const script = document.querySelector('script[src*="vox.strategicmachines.ai"]');
if (script) script.remove();
if ((window as any).voxWidget) {
(window as any).voxWidget.destroy();
}
}
}
Multi-Environment Setup
Use environment-specific widget keys to isolate development, staging, and production traffic:
Development
// Local development (http://localhost:3000)
window.voxConfig = {
tenantId: 'your-tenant-id',
agentId: 'your-agent-id',
widgetKey: 'w_dev_local_abc123'
};
Staging
// Staging environment (https://staging.yoursite.com)
window.voxConfig = {
tenantId: 'your-tenant-id',
agentId: 'your-agent-id',
widgetKey: 'w_staging_def456'
};
Production
// Production (https://yoursite.com)
window.voxConfig = {
tenantId: 'your-tenant-id',
agentId: 'your-agent-id',
widgetKey: 'w_prod_ghi789'
};
Best Practice: Use environment variables to manage keys:
window.voxConfig = {
tenantId: 'your-tenant-id',
agentId: 'your-agent-id',
widgetKey: process.env.NEXT_PUBLIC_VOX_WIDGET_KEY // Next.js
// or import.meta.env.VITE_VOX_WIDGET_KEY // Vite
// or process.env.REACT_APP_VOX_WIDGET_KEY // Create React App
};
Widget API and Events
The widget exposes a JavaScript API for programmatic control:
Opening and Closing
// Open the widget programmatically
window.voxWidget.open();
// Close the widget
window.voxWidget.close();
// Toggle the widget
window.voxWidget.toggle();
Event Listeners
// Widget loaded and ready
window.addEventListener('vox:ready', () => {
console.log('VOX widget is ready');
});
// Session started
window.addEventListener('vox:session:start', (event) => {
console.log('Session started:', event.detail.sessionId);
});
// Session ended
window.addEventListener('vox:session:end', (event) => {
console.log('Session ended:', event.detail);
// event.detail includes: sessionId, duration, messageCount
});
// Agent message received
window.addEventListener('vox:message', (event) => {
console.log('Agent said:', event.detail.text);
});
// Error occurred
window.addEventListener('vox:error', (event) => {
console.error('Widget error:', event.detail.message);
});
Custom Triggers
Trigger the widget from your own UI elements:
<button onclick="window.voxWidget.open()">
Talk to Our Assistant
</button>
<a href="#" onclick="event.preventDefault(); window.voxWidget.open();">
Need help? Chat with us
</a>
Troubleshooting
Widget Key Rejected
Error: Widget key validation failed or Unauthorized
Causes:
- Widget key doesn't match the configured origin
- Key has been revoked
- Tenant is inactive
Solutions:
- Verify the key in your tenant configuration matches the embedded key exactly
- Ensure the
originin the widget key config matches your website URL precisely:https://yoursite.com≠https://www.yoursite.comhttps://yoursite.com≠http://yoursite.com
- Create separate widget keys for
wwwand non-wwwversions if needed - Check that the tenant status is
activenottrialorsuspended
CORS Errors
Error: Access to fetch at 'https://vox.strategicmachines.ai' has been blocked by CORS policy
Causes:
- Origin mismatch between your domain and the widget key configuration
- Missing protocol (http vs https)
Solutions:
- Ensure your widget key's
originfield exactly matches your website's URL - Update the origin in your tenant config:
{ "widgetKeys": [ { "origin": "https://yoursite.com" // Must match exactly } ] } - For local development, add a key with
origin: "http://localhost:3000"
Microphone Permission Denied
Error: User sees "Microphone access denied" message
Causes:
- Browser doesn't support microphone API
- User explicitly denied permission
- Site is not served over HTTPS (required for microphone access)
Solutions:
- Ensure your site uses HTTPS in production (HTTP blocks microphone access)
- For local development, use
localhost(allowed over HTTP) - Provide clear messaging about why microphone access is needed
- Test microphone permissions with:
navigator.mediaDevices.getUserMedia({ audio: true }) .then(() => console.log('Microphone access granted')) .catch(err => console.error('Microphone access denied:', err));
Widget Not Appearing
Error: Widget script loads but no button appears
Causes:
- CSS z-index conflict
- Element positioning issue
- JavaScript error preventing initialization
Solutions:
- Check browser console for errors
- Increase
zIndexin config:window.voxConfig = { zIndex: 99999 }; - Verify the widget script loaded:
console.log('Widget loaded:', !!window.voxWidget); - Try a different position:
window.voxConfig = { position: 'bottom-left' };
Session Fails to Start
Error: Widget opens but conversation doesn't start
Causes:
- Rate limit exceeded
- Tenant concurrent session limit reached
- Agent configuration error
Solutions:
- Check browser console and network tab for error responses
- Verify tenant limits:
maxConcurrentCallsnot exceededmaxMonthlyMinutesnot exhausted
- Confirm agent configuration is valid (test with different agent if available)
- Review session start errors in the VOX platform monitoring dashboard
Performance Optimization
Lazy Loading
Load the widget only when needed to improve initial page load:
// Load widget on user interaction
document.getElementById('chat-button').addEventListener('click', function() {
if (!window.voxWidget) {
window.voxConfig = { /* config */ };
const script = document.createElement('script');
script.src = 'https://vox.strategicmachines.ai/widget.js';
script.onload = () => window.voxWidget.open();
document.head.appendChild(script);
} else {
window.voxWidget.open();
}
});
Defer Loading
Load the widget after the page is interactive:
// Load after DOMContentLoaded
document.addEventListener('DOMContentLoaded', function() {
window.voxConfig = { /* config */ };
const script = document.createElement('script');
script.src = 'https://vox.strategicmachines.ai/widget.js';
script.async = true;
document.head.appendChild(script);
});
Security Best Practices
Widget keys are public (embedded in HTML). Never use API secrets or tenant secrets in the widget config.
Always configure the exact origin for each widget key. Don't use wildcards or overly broad origins.
Update widget keys quarterly and revoke old keys to limit exposure from compromised keys.
Track widget session patterns to detect unusual traffic that might indicate abuse.
Next Steps
With your widget embedded, complete the deployment process: