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

  1. Go to Agents and open the agent you want to embed
  2. Switch to the Integrations tab
  3. 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.com and https://example.com/ all mean the same thing.
  • A www. prefix is stripped and subdomains are covered — example.com also allows www.example.com and app.example.com.
  • localhost, 127.0.0.1 and *.localhost are 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>
AttributeRequiredValuesDefault
data-agent-keyYesYour agent key
data-positionNobottom-right, bottom-left, top-right, top-leftbottom-right
data-themeNodark, light, systemsystem
data-button-colorNoA hex colour, e.g. #9333EA#9333EA
data-titleNoPanel heading shown to visitorsAI 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.
  • Esc closes 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 parameterValuesNotes
themedark, light, systemsystem follows each visitor's device setting
header0Hides the title bar — use when your own page already has a heading
titleAny textOverrides 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 background to your theme#0a0a0a for dark, #f9fafb for light, transparent for system — 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. localhost is exempt, so local development works over plain HTTP.

  • Permissions-Policy. If your site sends its own Permissions-Policy header, 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

PlatformWhere to paste the script
WordPressAppearance → Theme File Editor → footer.php, before the closing body tag — or a header/footer script plugin
ShopifyOnline Store → Themes → Edit code → theme.liquid, before the closing body tag
WebflowProject Settings → Custom Code → Footer Code
SquarespaceSettings → Advanced → Code Injection → Footer
WixSettings → Custom Code → Add Code to Body – end
Plain HTMLDirectly 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):

  1. src without www — 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.
  2. 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_KEY

Opened 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 seeCause and fix
No button appears at allThe 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 keyThe key is wrong or the agent was deleted. Re-copy it from the Integrations tab.
Embedding is not enabled for this agentThe Enable Website Embedding toggle is off.
Domain not allowed: yoursite.comAdd that domain to Allowed Domains and save.
Browser never asks for the microphoneThe 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 speaksThe 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 colourstheme and the iframe's inline background disagree — see the iframe section above.

Next steps

On this page

Website Embedding | Hello AI Connect