One script tag adds an assistant visitors can type to or talk to out loud. It answers from your knowledge base, in your chosen voice and language when they talk, takes people to the page they ask about, and can take their details when they want a follow-up. Chat and voice are switched on separately.
The agent answers only from its project's knowledge base. In AI Voice Agent → Knowledge Base, import your website with the crawler or add documents, and set them Live. Pick its voice and language on Voice & Language.
In AI Voice Agent → Website Widget, add your site's address under Allowed websites, then import your pages so the agent knows where it can take people.
Choose the colour, position and wording, turn on Chat, Voice or both under Channels, and use “Test live” to try it right there.
Copy the snippet and put it before the closing </body> tag on every page it should appear on. It loads asynchronously and blocks nothing.
<script async src="https://aiclosr.ai/agent.js"
data-portal="clsr_your_portal_key"
data-agent="your-project-slug"></script>| Attribute | What it does |
|---|---|
| data-portal | Your account's public embed key. It is safe to publish — it identifies your account, and only the websites you allow can use it. |
| data-agent | Which AI project answers, by its slug. Copy it from the settings page; leaving it out uses your default project. |
| data-auto="false" | Load the script without rendering anything, and start it yourself with ClosrAgent.init(). |
Paste the tag just before </body> in your page template.
Appearance → Theme File Editor → footer.php, before </body> — or use a header-and-footer plugin such as WPCode and add it to the footer.
Online Store → Themes → ⋯ → Edit code → layout/theme.liquid, before </body>.
Webflow: Site settings → Custom code → Footer code. Squarespace: Settings → Advanced → Code Injection → Footer. Wix: Settings → Custom code → Add code, body end, all pages.
A Custom HTML tag with the snippet, fired on All Pages. Tag Manager strips the data- attributes from some setups — if the agent does not appear, use the JavaScript API form instead.
Set navigation to “Your site's router” in the settings, and hand the agent your router so a move between pages never reloads the conversation:
<script async src="https://aiclosr.ai/agent.js"
data-portal="clsr_your_portal_key"
data-agent="your-project-slug"></script>
<script>
window.ClosrAgent = window.ClosrAgent || { q: [] };
// Move with your own router instead of a full page load.
ClosrAgent.q.push(["onNavigate", function (path) { router.push(path); }]);
</script>Mount this once, in your root layout:
"use client";
import { useEffect } from "react";
import { useRouter } from "next/navigation";
export function ClosrVoiceAgent() {
const router = useRouter();
useEffect(() => {
const load = () => {
window.ClosrAgent.onNavigate((path) => router.push(path));
window.ClosrAgent.init({
portal: "clsr_your_portal_key",
agent: "your-project-slug",
host: "https://aiclosr.ai",
});
};
if (window.ClosrAgent) return load();
const s = document.createElement("script");
s.src = "https://aiclosr.ai/agent.js";
s.dataset.auto = "false";
s.onload = load;
document.body.appendChild(s);
return () => window.ClosrAgent?.destroy();
}, [router]);
return null;
}Visitors speak and the agent answers out loud, with live captions. With typing on, anyone who can't or won't use a microphone types instead — typing never asks for microphone access.
Ask “where are your prices?” and the browser moves to your pricing page while the agent keeps talking. It only goes to pages in your site map; anything else is a polite “that isn't on this site”.
With “Take contact details” on, a visitor who wants a follow-up is asked for an email or phone, it is read back to them, and a contact is created in your CRM.
If call recording is on for the project, visitors see a notice and press Start before the microphone opens.
Every conversation uses your voice minutes. Conversation length, conversations per day and per visitor per hour are capped in the settings.
Transcripts, summaries, outcomes and recordings appear in Calls & Analytics next to your phone calls.
agent.js exposes window.ClosrAgent. Calls made before the script has loaded can be queued on ClosrAgent.q, as in the single-page app example above.
ClosrAgent.open(); // show the panel
ClosrAgent.start({ mic: true }); // start talking (call from a click)
ClosrAgent.sendText("Do you deliver on Sundays?");
ClosrAgent.stop(); // end the conversation
ClosrAgent.hide(); ClosrAgent.show();
ClosrAgent.on("lead", () => gtag("event", "voice_lead"));
ClosrAgent.on("message", ({ role, text }) => console.log(role, text));
ClosrAgent.getState(); // { ready, open, active, phase, micOpen, callId }| Event | When it fires |
|---|---|
| ready | The agent is enabled for this site and has rendered. |
| unavailable | The agent is off, or this website is not on the allowed list. |
| open / close | The panel opened or closed. |
| start | A conversation started (continued: true after a page change). |
| message | A visitor or agent turn finished: { role, text }. |
| navigate | The agent moved the visitor: { path, kind: "page" | "spa" | "scroll" }. |
| lead | Contact details were saved. |
| state | Connecting, listening or speaking: { phase, active, micOpen }. |
| end | The conversation ended: { callId, durationSec, turns }. |
| error | The conversation failed: { message }. |
Each event is also dispatched on window as closr-agent:<name>, for tag managers.
Three events start automations and are delivered to your webhook endpoints: voice_widget.conversation_started, voice_widget.conversation_ended (with the summary and outcome) and voice_widget.lead_captured (with the contact).
conversation_ended webhook body
{
"id": "…",
"type": "voice_widget.conversation_ended",
"entity": { "kind": "session", "id": "CALL_ID" },
"occurredAt": "2026-09-16T10:04:12.000Z",
"payload": {
"callId": "CALL_ID",
"agentId": "…",
"site": "www.yourbusiness.com",
"durationSec": 142,
"turnCount": 11,
"summary": "Asked about wedding cake prices and delivery to Petaling Jaya.",
"outcome": "contained",
"intent": "pricing"
}
}With an API key (Settings → Integrations → API keys) you can read and change every website agent and pull its conversations. Updates go through the same checks as the settings page.
# Read every project's website agent settings
curl https://aiclosr.ai/api/v1/voice-widgets \
-H "Authorization: Bearer $CLOSR_API_KEY"
# Update one (write key)
curl -X PATCH https://aiclosr.ai/api/v1/voice-widgets/your-project-slug \
-H "Authorization: Bearer $CLOSR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"accentColor":"#0f766e","launcherLabel":"Ask us anything"}'
# Conversations, then one with its transcript
curl "https://aiclosr.ai/api/v1/voice-widgets/your-project-slug/conversations?limit=20" \
-H "Authorization: Bearer $CLOSR_API_KEY"
curl https://aiclosr.ai/api/v1/voice-widgets/your-project-slug/conversations/CALL_ID \
-H "Authorization: Bearer $CLOSR_API_KEY"| Check | Why |
|---|---|
| Is it on? | A switched-off agent renders nothing, on purpose. |
| Allowed websites | The address must match exactly, including https:// and www. A page on a site that isn't listed gets nothing. |
| Show the launcher on | Set to “Only pages listed” hides it on pages outside the site map. |
| Content-Security-Policy | Strict sites must allow script-src and connect-src https://closr.my, connect-src wss://generativelanguage.googleapis.com, and worker-src blob:. |
| Microphone blocked | The agent still works by typing. Visitors can re-allow the microphone from the address bar. |
| “Very busy today” | The daily conversation cap was reached. Raise it in Behaviour. |
Knowledge base, voice, navigation and lead capture — configured in Closr, running on your site.
7-day Pro trial, no card required. Set up in under three minutes.