Website Embedding
Put a live voice agent on your own website with a single script tag, or drop it into a section of the page as an iframe.
Any agent can be embedded on your own site so visitors talk to it in the browser — no phone number, no app to install. There are two ways to do it, both driven by the same agent key:
- Floating widget — one script tag adds a launcher button in the corner of every page.
- Iframe — you place the call panel exactly where you want it in your layout.
Both run the identical call experience, so pick whichever fits your page.
1. Turn on embedding
- Go to Agents and open the agent you want to embed
- Switch to the Integrations tab
- Toggle Enable Website Embedding on
An agent key is generated for you the first time you enable it. It looks like helloai_a1b2c3d4e5f6g7h8 and identifies your agent when a browser starts a call.
The agent key is public by design
The key ships inside your page's HTML, so anyone who views source can read it. That is unavoidable for any browser embed. The Allowed Domains list below — not secrecy — is what stops the key being reused elsewhere. Set it before you go live. If a key does leak somewhere you don't control, use Regenerate Key and re-paste the snippet.
2. Restrict where it can run
Under Allowed Domains, add every domain that is allowed to embed this agent, then re-copy your snippet.
- Leave the list empty and any site can embed the agent and bill calls to your account.
- Entries are reduced to a bare hostname, so
example.com,www.example.comandhttps://example.com/all mean the same thing. - A
www.prefix is stripped and subdomains are covered —example.comalso allowswww.example.comandapp.example.com. localhost,127.0.0.1and*.localhostare always allowed, so you can build against a real agent before deploying.
A call from a domain that isn't listed is rejected with Domain not allowed: <host>.
The allow-list is a deterrent, not a hard security boundary. The widget runs as an iframe on our origin, so the host page's identity is reported by the browser rather than proven — a determined caller can forge it. Treat the domain list as protection against casual key reuse, and don't share one key across sites you don't control.
3. Copy your snippet
The Embed Code box in the Integrations tab always shows a snippet with your key and current appearance settings already filled in. The sections below explain each option.
Floating widget
Paste this just before the closing </body> tag of your site. It appears on every page you paste it into.
<!-- HelloAI Voice Agent Widget -->
<script
src="https://www.helloaiconnect.com/embed/helloai-widget.js"
data-agent-key="helloai_YOUR_KEY"
data-position="bottom-right"
data-button-color="#9333EA"
data-theme="dark"
data-title="AI Assistant"
></script>| Attribute | Required | Values | Default |
|---|---|---|---|
data-agent-key | Yes | Your agent key | — |
data-position | No | bottom-right, bottom-left, top-right, top-left | bottom-right |
data-theme | No | dark, light, system | system |
data-button-color | No | A hex colour, e.g. #9333EA | #9333EA |
data-title | No | Panel heading shown to visitors | AI Assistant |
Anything unrecognised falls back to the default rather than breaking the widget — data-button-color in particular only accepts literal hex, since it is written into a stylesheet.
How it behaves:
- All of the widget's markup lives in a shadow root, so your site's CSS and ours can't reach each other. It needs no CSS of its own on your page.
- Including the tag twice for the same key still renders one launcher.
- The call panel is created the first time it's opened and then only hidden. Minimising the panel does not hang up — a green pulsing dot on the launcher shows a call is still running.
Esccloses the panel.
Iframe
Use this when you want the agent inside a section of the page instead of floating over it. Adjust width and height to fit your layout.
<!-- HelloAI Voice Agent Iframe -->
<iframe
src="https://www.helloaiconnect.com/embed/helloai_YOUR_KEY?theme=dark&header=0"
width="400"
height="500"
frameborder="0"
allow="microphone"
style="border-radius: 16px; background: #0a0a0a;"
></iframe>| Query parameter | Values | Notes |
|---|---|---|
theme | dark, light, system | system follows each visitor's device setting |
header | 0 | Hides the title bar — use when your own page already has a heading |
title | Any text | Overrides the heading text (URL-encode it) |
Two things to keep consistent:
allow="microphone"is required. Without it the browser blocks the mic and the call connects with nothing to hear.- Match the inline
backgroundto yourtheme—#0a0a0afor dark,#f9fafbfor light,transparentforsystem— otherwise the box paints one colour while the widget renders the other.
Use www.helloaiconnect.com exactly — the host matters
https://helloaiconnect.com (without www) redirects to https://www.helloaiconnect.com. In a normal tab that's harmless, but inside an iframe it silently kills the microphone: allow="microphone" grants the mic to the frame's original src origin, and after the redirect the page is running on a different origin outside that grant. The result is the most confusing failure there is — the agent greets you, the timer runs, but it never hears you. Always write the www host in the iframe src (the widget script corrects this automatically).
4. Requirements for the host page
-
HTTPS. Browsers only grant microphone access on secure origins.
localhostis exempt, so local development works over plain HTTP. -
Permissions-Policy. If your site sends its own
Permissions-Policyheader, it must delegate the mic to us:Permissions-Policy: microphone=(self "https://www.helloaiconnect.com") -
Content-Security-Policy. If your site sends a CSP, allow our origin in both directives:
script-src 'self' https://www.helloaiconnect.com; frame-src 'self' https://www.helloaiconnect.com;You don't need to open
connect-src. The LiveKit media connection is made from inside our iframe, which is governed by our policy, not yours.
Adding it to common platforms
| Platform | Where to paste the script |
|---|---|
| WordPress | Appearance → Theme File Editor → footer.php, before the closing body tag — or a header/footer script plugin |
| Shopify | Online Store → Themes → Edit code → theme.liquid, before the closing body tag |
| Webflow | Project Settings → Custom Code → Footer Code |
| Squarespace | Settings → Advanced → Code Injection → Footer |
| Wix | Settings → Custom Code → Add Code to Body – end |
| Plain HTML | Directly in your index.html, before the closing body tag |
In Next.js, use the Script component so it loads once per session rather than on every client-side navigation:
import Script from "next/script";
<Script
src="https://www.helloaiconnect.com/embed/helloai-widget.js"
data-agent-key="helloai_YOUR_KEY"
data-position="bottom-right"
data-theme="dark"
strategy="afterInteractive"
/>Driving the call from your own button
The widget doesn't expose a JavaScript API for opening or closing it — the launcher button is the entry point. If you want your own "Talk to us" button to start the conversation, use the iframe embed and control its visibility yourself: keep the iframe hidden in your layout and reveal it when your button is clicked.
The embed page reports call status to whatever page contains it, so your UI can react to a call starting or ending:
window.addEventListener("message", (event) => {
// Only trust messages coming from our iframe.
if (event.origin !== "https://www.helloaiconnect.com") return;
const data = event.data;
if (!data || data.source !== "helloai-embed" || data.type !== "status") return;
// data.status is "idle" | "connecting" | "connected" | "ended"
console.log("Voice agent status:", data.status);
});Status is all that is sent — no transcript or caller data crosses the frame boundary.
Complete working example
This is the full setup from a real integration (a "Start voice call" button on a marketing page that reveals the call panel), verified working end-to-end. Every detail below matters — it condenses everything this guide warns about into one copy-paste block:
<!-- Your own call-to-action button -->
<button id="talk-to-us">Start voice call</button>
<!-- The call panel: hidden until the button is clicked.
src MUST use www.helloaiconnect.com — see the host warning above.
allow="microphone" MUST be present. -->
<iframe
id="voice-agent"
src="https://www.helloaiconnect.com/embed/helloai_YOUR_KEY?theme=light&header=0"
allow="microphone"
width="380"
height="560"
style="border: 0; border-radius: 16px; background: #f9fafb; display: none;"
></iframe>
<script>
const frame = document.getElementById("voice-agent");
const button = document.getElementById("talk-to-us");
button.addEventListener("click", () => {
frame.style.display = "block"; // reveal, never recreate — recreating drops a call
});
// Optional: react to call status (e.g. disable your button during a call).
window.addEventListener("message", (event) => {
if (event.origin !== "https://www.helloaiconnect.com") return;
const data = event.data;
if (!data || data.source !== "helloai-embed" || data.type !== "status") return;
button.disabled = data.status === "connecting" || data.status === "connected";
});
</script>The two mistakes that break this setup in practice, both producing the same confusing symptom (the agent greets the visitor but never hears them):
srcwithoutwww— the redirect moves the page outside the microphone grant. This exact bug shipped on a real site and took a day to diagnose; the fix was changing nothing but the host.- Missing
allow="microphone"— the browser refuses capture inside the frame while everything else still works.
If you already integrated against the non-www host, the only change needed is the src (and the event.origin comparison, if you use the status listener).
Testing without a website
Open the embed page directly in a browser:
https://www.helloaiconnect.com/embed/helloai_YOUR_KEYOpened on its own like this it isn't embedded anywhere, so the domain allow-list doesn't apply and you can confirm the agent answers before touching your site.
Calls, history, and billing
Embedded calls are production calls, exactly like phone calls:
- They appear in Call History with transcript and recording.
- Their duration counts toward your plan's minutes.
- Testing from the agent's Mic Test tab does not consume production minutes — talking to a live embed does.
Troubleshooting
| What you see | Cause and fix |
|---|---|
| No button appears at all | The tag isn't running. Check it sits before the closing body tag and look in the browser console — a missing key logs [HelloAI] Widget not started: the script tag needs a data-agent-key attribute. |
Invalid agent key | The key is wrong or the agent was deleted. Re-copy it from the Integrations tab. |
Embedding is not enabled for this agent | The Enable Website Embedding toggle is off. |
Domain not allowed: yoursite.com | Add that domain to Allowed Domains and save. |
| Browser never asks for the microphone | The page isn't served over HTTPS, or an iframe embed is missing allow="microphone". |
| Agent greets you but never hears you (embedded only; the direct link works) | Your iframe src uses helloaiconnect.com without www. The redirect moves the page outside the mic grant — see the host warning above. Change the src to www.helloaiconnect.com. |
| Panel connects but nobody speaks | The call is running but the agent isn't. Check the agent is active and its model and voice providers are configured, then open the call in Call History. |
| Widget shows the wrong colours | theme and the iframe's inline background disagree — see the iframe section above. |
Next steps
- Create your first agent if you don't have one to embed yet
- Phone numbers to take the same agent onto a phone line
- Contact support if an embed still won't connect

