Website Widgets
Embed your voice agent on any website so visitors can start a voice conversation directly from the browser. The Widgets tab provides embed codes, customization options, and domain security settings.
The Callback Widget works very differently from the widgets on this page — instead of starting a browser voice call, it collects the visitor's phone number and has your agent dial them back. See the dedicated Callback Widget page.
Widget Types
Hanc.AI offers four in-browser widget types, plus a separate Callback Widget for phone callbacks:
| Widget | Tag Name | Description | Best For |
|---|---|---|---|
| Floating Widget | hanc-ai-floating-call | Orb button that floats over your page | Always-visible call-to-action |
| Pill Widget | hanc-ai-pill-call | Compact pill-shaped button placed inline in your content | Minimal footprint inside an existing layout |
| Pill Floating Widget | hanc-ai-pill-floating-call | Same compact pill shape as Pill, but floats with the page like the Floating widget | When you want a pill aesthetic that follows the visitor as they scroll |
| Inline Widget | hanc-ai-inline-call | Full-size call button embedded in page content | Dedicated "Talk to Us" sections |
| Callback Widget | hanc-ai-callback | Phone-number form; agent calls the visitor back | Lead-gen pages, high-intent landing pages — see Callback Widget |
Common Attributes
All widget types support these core attributes:
| Attribute | Required | Description | Default |
|---|---|---|---|
agent-id | Yes | Your agent's unique identifier | — |
voice-service-url | No | Override the voice service URL | Auto-detected |
api-base-url | No | Override the API base URL | Auto-detected |
Display Attributes
| Attribute | Description | Values | Default |
|---|---|---|---|
position | Widget position on the page | bottom-right, bottom-left, top-right, top-left, static | bottom-right |
size | Widget size in pixels | Number | 120 |
theme | Color theme name | See Color Themes | default |
Button Text Attributes
| Attribute | Description | Default |
|---|---|---|
button-start-text | Text shown on the idle button | "Call" |
button-connecting-text | Text shown while connecting | "Connecting..." |
button-end-text | Text shown during an active call | — |
Terms Attributes
| Attribute | Description | Default |
|---|---|---|
terms-enabled | Enable consent dialog before call | false |
terms-content | Markdown-formatted consent text | "" |
terms-url | Link to your Terms & Conditions page | "https://hanc.ai/terms" |
privacy-url | Link to your Privacy Policy page | "https://hanc.ai/privacy" |
Terms attributes set on the HTML element are overridden by the agent's widget settings fetched from the API, unless skip-fetch is set to true.
Sound Attributes
| Attribute | Description | Default |
|---|---|---|
sound-enabled | Enable call start/end sounds | true |
sound-volume | Sound effect volume | 0.25 |
sound-preset | Sound preset identifier | "1" |
Color Themes
Customize widget appearance with 11 built-in color themes:
| Theme | Value |
|---|---|
| Default | default |
| Purple | purple |
| Blue | blue |
| Cyan | cyan |
| Emerald | emerald |
| Amber | amber |
| Tangerine | tangerine |
| Rose | rose |
| Ember | ember |
| Black | black |
| White | white |
Each theme has both dark and light variants. Set the theme via the theme attribute in the embed code, or configure it in the agent's widget settings.
Embed Examples
Floating Widget
<hanc-ai-floating-call agent-id="YOUR_AGENT_ID"></hanc-ai-floating-call>
<script src="https://unpkg.com/hanc-webrtc-widgets" async type="text/javascript"></script>
Floating Widget with Theme and Position
<hanc-ai-floating-call
agent-id="YOUR_AGENT_ID"
theme="emerald"
position="bottom-left"
size="140"
></hanc-ai-floating-call>
<script src="https://unpkg.com/hanc-webrtc-widgets" async type="text/javascript"></script>
Pill Widget
<hanc-ai-pill-call agent-id="YOUR_AGENT_ID"></hanc-ai-pill-call>
<script src="https://unpkg.com/hanc-webrtc-widgets" async type="text/javascript"></script>
Inline Widget
<hanc-ai-inline-call agent-id="YOUR_AGENT_ID"></hanc-ai-inline-call>
<script src="https://unpkg.com/hanc-webrtc-widgets" async type="text/javascript"></script>
The script URL above always loads the latest released widget — your site picks up improvements automatically, and @latest is the default when no version is specified. If you need to lock to a fixed release, pin explicitly by appending the version you want, e.g. https://unpkg.com/hanc-webrtc-widgets@X.Y.Z.
Widget Events
Widgets emit events that you can listen for in JavaScript:
| Event | Description |
|---|---|
status-changed | Fired when the call status changes |
connecting | Call is being established |
connected | Call is active |
idle | No active call |
error | An error occurred |
audio-track | Remote audio track received (for visualization) |
local-audio-track | Local microphone audio track (for visualization) |
microphone-enabled | Microphone was enabled |
microphone-disabled | Microphone was disabled |
call-start | Fired when a call starts successfully |
call-end | Fired when the call ends |
Example: Listening for Events
const widget = document.querySelector('hanc-ai-floating-call');
widget.addEventListener('call-start', () => {
console.log('Call started');
});
widget.addEventListener('call-end', () => {
console.log('Call ended');
});
Letting the Agent Open Pages
During a browser call the agent can ask your site to open a page — "let me show you the pricing" — and the visitor sees it happen while they keep talking.
Your page stays in charge. The agent sends a request; your code decides what it means. Opening a URL, switching a tab, scrolling to a section and expanding an accordion are all valid answers.
This takes three steps, and none of them work alone.
Step 1 — Tell the agent which pages exist
The agent cannot see your site map. It only ever asks for a path you gave it, so list them in the agent's prompt or knowledge base:
Pages on our site:
/pricing — plans and prices
/contact — contact form and phone number
/product/crm — the CRM
Skip this step and the agent has nothing to ask for, so it never tries.
Step 2 — Handle the request on your page
Add this once, anywhere after the widget script. The event travels up through the page, so document is a fine place to listen:
<script>
document.addEventListener('agent-command', (event) => {
const { type, payload } = event.detail;
if (type === 'navigate') {
// Single-page app: route without reloading — the call keeps running.
router.push(payload.path);
// Classic site: open a second tab, so this one (and the call) survives.
// window.open(payload.path, '_blank');
event.preventDefault(); // ← tells the agent you handled it
}
});
</script>
The call lives in this page. window.location.href = … unloads the document and the conversation goes with it — the visitor is cut off mid-sentence. Route client-side if you have a router, or open the page in a new tab. There is no reconnect: nothing survives a reload.
preventDefault() is not optionalIt is the only way to say "I handled it". Leave it out and the agent is told the site does not support navigation: it stops trying for the rest of the call and falls back to describing where to click. Nothing appears in the browser console — the page simply looks like it ignored the request, because it did.
Step 3 — Try it
Call your agent from the site and ask for a page by name. Two things should happen: the page opens, and the agent says something like "here are the prices" rather than "you can find them in the menu".
Answering the agent with data
Some commands are questions rather than instructions. They arrive the same way, but you reply with respond():
| Command | The agent is asking | You reply with |
|---|---|---|
navigate | "open this path" | nothing — just preventDefault() |
page_context | "what is the visitor looking at?" | anything useful: path, title, product |
cart_state | "what is in their basket?" | items, totals, currency |
<script>
document.addEventListener('agent-command', (event) => {
const { type, respond } = event.detail;
if (type === 'page_context') {
event.preventDefault();
respond({ path: location.pathname, title: document.title });
}
});
</script>
You have about a second to answer after preventDefault(), so an await is fine but a slow API call is not. Answer with what you already have.
What the agent may and may not ask for
- Paths on your own site only. A path must start with
/. Anything that could leave your domain —//evil.com,https://…, backslashes — is refused before it reaches your page. An agent cannot send your visitors somewhere else. - Browser calls only. There is no page to open on a phone call, so these commands do not exist there.
- One refusal is enough. If your page does not confirm the first request, the agent stops asking for the rest of the call. It will not keep trying, and — importantly — it will not tell the visitor it opened something it did not.
Log every command before you filter it: document.addEventListener('agent-command', e => console.log(e.detail)). If you see navigate in the console, the agent is doing its part and the missing piece is preventDefault() or your own handler. If you see nothing, the agent was never told the path exists — go back to step 1.
Technical Requirements
Widgets require the visitor's browser to support:
- WebGL 2.0 — for rendering
- Web Audio API — for audio processing
- WebRTC — for real-time voice communication
All modern browsers (Chrome, Firefox, Safari, Edge) support these technologies.
Domain Restrictions
Control which websites can embed your agent widget.
Always Allowed Domains
The following domains are always allowed regardless of configuration:
hanc.ai(and subdomains)hanc.me(and subdomains)localhost
Allow All Domains
By default, your widget can be embedded on any website. Toggle "Allow all domains" in widget settings to restrict this.
Restrict to Specific Domains
When restricted, add each domain that should be allowed:
- Enter domain names without
https://(e.g.,example.com) - Subdomains need separate entries (e.g.,
www.example.com,shop.example.com) - Ports can be specified (e.g.,
localhost:3000) - Maximum 50 domains can be whitelisted
For production agents, restrict widgets to your own domains to prevent unauthorized embedding.
Terms & Conditions
Enable a consent dialog before callers can start a conversation.
Configuration
| Setting | Description |
|---|---|
| Enable Terms | Toggle terms dialog on/off |
| Terms Content | Markdown-formatted consent text shown to users (max 5,000 characters) |
| Terms URL | Link to your full Terms & Conditions page |
| Privacy URL | Link to your Privacy Policy page |
When enabled:
- Users see a consent dialog before starting a call
- They must click "Agree" to proceed
- Consent is stored locally in the browser
- Reset Consent button clears stored consent for testing
Content Formatting
Terms content supports Markdown formatting:
- Use
####for headings - Use
**bold**for emphasis - Use line breaks for readability
Related
- Callback Widget — Phone callbacks for visitors who'd rather not talk in-browser
- Voice Agents Overview
- Settings — Agent settings including widget configuration
- Integrations — API keys and phone number setup