Web widget: talk to your AI from a web page
@morevoice/web puts a voice call with your assistant on a web page. It has two parts:
<morevoice-talk-button>, a web component: one click and the visitor is talking to your assistant, with a live status, mute, hang-up, captions and the AI notice. It works in any page and any framework, in Hebrew (right to left) and English, in light and dark.MoreVoiceCall, the call client the button is built on, for your own interface: start a call, follow its events, mute, send keys and hang up.
The call runs over WebRTC from the visitor’s microphone to MoreVoice, and the same call object, transcript, recording and webhooks follow as for a phone call (with direction: "browser").
Add the button
Section titled “Add the button”Load the script once, and place the element where the button should be:
<!doctype html><html lang="he" dir="rtl"> <head> <meta charset="utf-8" /> <title>Acme Insurance</title> <script src="https://cdn.jsdelivr.net/npm/@morevoice/web/dist/morevoice-web.js" defer></script> </head> <body> <h1>שאלות על הפוליסה? פשוט לשאול.</h1> <morevoice-talk-button server="https://voice.example.com" assistant="3cYbE6uYvGkH8w4ZK1rTqd" org-name="Acme Insurance" lang="he" captions ></morevoice-talk-button> </body></html>The script registers the element and exposes window.MoreVoice (MoreVoiceCall, MoreVoiceCallError, defineTalkButton) for pages without a bundler. With a bundler, install the package and import the element instead:
import "@morevoice/web/define"; // registers <morevoice-talk-button>import type { MoreVoiceTalkButton } from "@morevoice/web/talk-button";
const button = document.querySelector<MoreVoiceTalkButton>("morevoice-talk-button");if (button) button.metadata = { page: location.pathname };Attributes
Section titled “Attributes”| Attribute | What it does |
|---|---|
server |
Your MoreVoice server’s address, where your team signs in. By default, the page’s own origin. |
assistant |
The assistant to talk to: its ID in the dashboard. |
lang |
he or en. By default, the page’s language. Hebrew lays the button out right to left. |
label |
The button’s text, instead of “Talk to us” / “דברו איתנו”. |
org-name |
Your business’s name, in the AI notice: “You are talking with an AI agent of Acme Insurance. The call is recorded and transcribed.” |
captions |
Show what is said, live, under the button. |
variant="floating" |
A floating button in the page’s corner (the end side: bottom-left in Hebrew), instead of inline. |
theme |
light or dark. By default, the visitor’s system setting. |
notice="off" |
Hide the AI notice, but only after the server confirmed that this assistant says it is an AI at the start of the call. The first call always shows it before the microphone opens. |
Events, properties and methods
Section titled “Events, properties and methods”The element sends events that bubble out of its shadow root, each with the details in event.detail:
| Event | When |
|---|---|
morevoice-status |
The call’s own status changed: starting (asking for the microphone), connecting, active, ending, ended. |
morevoice-answer |
MoreVoice answered: callId is the call’s ID, for your logs and for the API. |
morevoice-state |
The assistant is listening, thinking, using a tool or speaking. |
morevoice-transcript |
A line of the conversation, partial or final, with who said it. |
morevoice-ended |
The call ended, with a reason. |
morevoice-error |
Something went wrong: code is mic_denied, mic_unavailable, unsupported, webrtc_failed, ws_closed or server_error. |
Set metadata (string key–values kept on the call, like metadata on an API call) and auth (below) as properties. start() and hangup() do what the buttons do, and call is the current MoreVoiceCall.
Theming
Section titled “Theming”The button takes your brand’s colours from CSS custom properties on the element: --mv-accent, --mv-accent-2 (the gradient), --mv-on-accent, --mv-bg, --mv-fg, --mv-muted, --mv-line, --mv-bad, --mv-font and --mv-radius. For more, style its parts with ::part(): button, bar, orb, status, timer, mute, end, notice, caption, error and info.
<style> morevoice-talk-button { --mv-accent: #0f766e; --mv-accent-2: #14b8a6; --mv-radius: 12px; } morevoice-talk-button::part(notice) { font-size: 12px; }</style>The button respects reduced motion and Windows high-contrast mode, keeps keyboard focus where the visitor expects it, and announces the call’s state to screen readers.
Who may call
Section titled “Who may call”Every call is authorised by the element’s auth property, or a function that returns it per call (a one-time credential must be fresh each time):
auth |
For |
|---|---|
{ kind: "cookie" } (the default) |
People signed in to MoreVoice on the same site: internal tools and test pages on your MoreVoice domain. |
{ kind: "clientToken", value } |
Your website’s visitors: a short-lived client token your server mints for one call. |
{ kind: "ticket", value } |
MoreVoice’s own apps (the softphone, the browser extension). |
Create a client token
Section titled “Create a client token”POST /v1/client_tokens · scope web_calls:write · API reference
Mint, on your server, a short-lived token a browser uses to start one call to one assistant with @morevoice/web (MoreVoiceWeb.start({ token })). The token works once, until expires_at; bind it to your site with origin. It carries the call’s ID, so you can follow the call (webhooks, GET /v1/calls/{id}/events) before it starts.
| Field | Required | What it does |
|---|---|---|
agent_ |
The agent whose softphone the token opens (the embeddable softphone; /ws/call refuses agent tokens). |
|
assistant_ |
The assistant the browser will talk to. | |
metadata |
Up to 50 key/value pairs (keys up to 40 characters, values up to 500) you attach to an object. Returned as sent. | |
origin |
The only website the token works from (https://shop.example): the browser’s Origin must match. Strongly recommended. http is accepted for localhost only. |
|
ttl_ |
How long the token can be used to start the call, in seconds (300–900, default 300). The call itself may run longer. |
curl -X POST https://api.morevoice.ai/v1/client_tokens \ -H "Authorization: Bearer $MOREVOICE_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "assistant_id": "asst_8tRPaZp5hLMbrGqdJ9AmNa", "metadata": { "crm_contact_id": "0031x00000AbCdE" }, "origin": "https://shop.example", "ttl_s": 300}'import MoreVoice from "@morevoice/sdk";
const mv = new MoreVoice(); // MOREVOICE_API_KEY from the environment
const clientToken = await mv.clientTokens.create({ assistant_id: "asst_8tRPaZp5hLMbrGqdJ9AmNa", metadata: { crm_contact_id: "0031x00000AbCdE", }, origin: "https://shop.example", ttl_s: 300,});console.log(clientToken);from morevoice import MoreVoice
client = MoreVoice() # MOREVOICE_API_KEY from the environment
client_token = client.client_tokens.create({ "assistant_id": "asst_8tRPaZp5hLMbrGqdJ9AmNa", "metadata": { "crm_contact_id": "0031x00000AbCdE", }, "origin": "https://shop.example", "ttl_s": 300,})print(client_token)Start a web call
Section titled “Start a web call”POST /v1/web_calls · scope web_calls:write · API reference
From the browser with a publishable key (mv_live_pk_…), or from your server with a secret key: returns the call ID and a single-use client token for /ws/call. With a publishable key the assistant must be public, the page must be one of the key’s allowed origins (and of the assistant’s widget origins, when it lists any), and each visitor may start 10 web calls a minute. The org’s concurrent-call quota applies.
| Field | Required | What it does |
|---|---|---|
assistant_ |
Yes | The assistant to talk to. With a publishable key it must be public (widget settings). |
metadata |
Up to 50 key/value pairs (keys up to 40 characters, values up to 500) you attach to an object. Returned as sent. |
curl -X POST https://api.morevoice.ai/v1/web_calls \ -H "Authorization: Bearer $MOREVOICE_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: $(uuidgen)" \ -d '{ "assistant_id": "asst_8tRPaZp5hLMbrGqdJ9AmNa", "metadata": { "page": "/pricing" }}'import MoreVoice from "@morevoice/sdk";
const mv = new MoreVoice(); // MOREVOICE_API_KEY from the environment
const webCall = await mv.webCalls.create({ assistant_id: "asst_8tRPaZp5hLMbrGqdJ9AmNa", metadata: { page: "/pricing", },});console.log(webCall);from morevoice import MoreVoice
client = MoreVoice() # MOREVOICE_API_KEY from the environment
web_call = client.web_calls.create({ "assistant_id": "asst_8tRPaZp5hLMbrGqdJ9AmNa", "metadata": { "page": "/pricing", },})print(web_call)Build your own call UI
Section titled “Build your own call UI”MoreVoiceCall is the button without the button: under 8 kB gzipped, no dependencies, with typed events. Use it for a call screen of your own, or inside a framework component:
import { MoreVoiceCall, MoreVoiceCallError } from "@morevoice/web";
const call = new MoreVoiceCall();call.on("answer", ({ callId }) => console.log("call", callId));call.on("state", ({ state }) => console.log("assistant is", state));call.on("transcript", (line) => { if (line.final) console.log(`${line.role}: ${line.text}`);});call.on("ended", ({ reason }) => console.log("ended:", reason));call.on("error", ({ code, message }) => console.warn(code, message));
document.querySelector("#talk")?.addEventListener("click", async () => { try { await call.start({ server: "https://voice.example.com", assistantId: "3cYbE6uYvGkH8w4ZK1rTqd", metadata: { page: location.pathname } }); } catch (err) { if (err instanceof MoreVoiceCallError && err.code === "mic_denied") alert("Allow the microphone to talk to us."); }});document.querySelector("#mute")?.addEventListener("click", () => call.mute(!call.muted));document.querySelector("#hangup")?.addEventListener("click", () => call.hangup());sendDtmf("1") presses a key for an IVR or a flow’s keypad menu. localStream and remoteStream give you the audio for a level meter, and audio.output in start() plays the assistant through an <audio> element of yours.
Browsers and privacy
Section titled “Browsers and privacy”- The page must be served over HTTPS (or from
localhost): browsers only open the microphone on secure pages. - The visitor is asked for the microphone when they click, never before. If they block it, the button tells them how to allow it and try again.
- The AI notice is shown before the microphone opens. Keep it, unless your assistant says it is an AI at the start of every call (
notice="off"checks that). - Leaving the page, or removing the element (a single-page app’s navigation), ends the call: an open microphone nobody is watching is worse than a dropped call.
For React apps, see React; to follow the call from your server while it runs, see real-time events.