Embed

Drop a live voice agent on any website in two lines

The <speechify-agent> web component is the shortest path from “I have an agent ID” to “my users are talking to it on my landing page”. One script tag, one element, no realtime plumbing to wire up.

1<script src="https://api.speechify.ai/v1/widget/agents.js"></script>
2<speechify-agent agent-id="a_01HS..."></speechify-agent>

That’s the whole integration for a public agent. Private agents use the same tag with a server-minted session token (see below).

Two modes

Public agent (direct embed)

Flip the Public toggle on the agent’s Embed tab in the console, add the domains you want the widget to work on to the origin allowlist, and paste the snippet into your page. The widget calls POST /v1/agents/{id}/sessions unauthenticated; the server verifies that Origin matches your allowlist before minting a session.

1<script src="https://api.speechify.ai/v1/widget/agents.js"></script>
2<speechify-agent agent-id="a_01HS..."></speechify-agent>

Use this for marketing sites, demo pages, and anywhere the agent conversation is the product itself.

Security

  • Embed only works from origins you explicitly allowlist on the agent. An empty allowlist with the public toggle on means “any origin accepted” — intended for open demos; enable deliberately.
  • No subdomain wildcards. Add each origin exactly (e.g. https://app.example.com, https://www.example.com).
  • The session endpoint is per-IP rate-limited. Repeat abuse from one source is throttled without affecting legitimate users.
  • The agent owner is always the billed principal regardless of who triggered the session.

Hostname allowlist

An additional, narrower gate applied at session-create time for public agents. Set it in the console under Embed tab → Hostname allowlist, or via PATCH /v1/agents/{id} with the hostname_allowlist field.

  • Up to 10 entries, exact hostname match (no wildcards, no subdomain matching). Scheme and port are ignored; only the Origin header’s hostname is checked.
  • Empty or omitted means “no hostname enforcement”, so the session is minted as usual. Use this gate when you want the widget to work only from a known set of production hostnames, even though the agent is public.
  • When non-empty, a request whose Origin hostname is not in the list is rejected with 403.
  • Good hostnames: example.com, app.example.com. Not accepted: *.example.com, https://example.com, example.com:8080.

Private agent (server-minted token)

Keep the agent private, mint a short-lived session token on your backend with your API key, and pass it to the widget. The API key never reaches the browser.

1# On your backend (Flask / FastAPI / etc).
2from speechify import Speechify
3
4client = Speechify() # uses SPEECHIFY_API_KEY
5
6session = client.tts.agents.create_session(id=agent_id)
7# Return session.url + session.token to your browser via your own API.

Browser side:

1<script src="https://api.speechify.ai/v1/widget/agents.js"></script>
2<speechify-agent
3 session-token="<token from your backend>"
4 session-url="<url from your backend>">
5</speechify-agent>

Attributes

AttributeRequiredDescription
agent-ideither this or session-token+session-urlPublic-agent mode. Widget calls the session endpoint directly.
session-tokenwith session-urlPrivate-agent mode. Pre-minted token from your backend.
session-urlwith session-tokenRealtime URL paired with the token.
user-identityoptionalOpaque identifier for the end-user; stamped onto the conversation for later lookup.
api-baseoptionalOverride the API origin. Defaults to https://api.speechify.ai.

Per-call overrides

One embed can serve many customer segments without minting new agents. These attributes are forwarded to POST /v1/agents/{id}/sessions each time the caller starts a call, so you can update them between calls (e.g. after a user logs in) and the next call picks them up.

AttributeDescription
override-promptReplace the agent’s system prompt for this call only.
override-first-messageReplace the first message the agent speaks when the call opens.
override-voice-idOverride the voice by voice ID.
override-languageOverride the conversation language (BCP-47, e.g. en-US).
dynamic-variablesJSON object string passed through to the session as dynamic_variables. The shape is your contract with the agent prompt, e.g. '{"customer_tier":"pro"}'.
1<speechify-agent
2 agent-id="a_01HS..."
3 override-prompt="You are a support bot for Acme. Greet the caller by name."
4 override-first-message="Hi Ada, how can I help?"
5 override-voice-id="v_123"
6 override-language="en-US"
7 dynamic-variables='{"customer_tier":"pro","account_id":"acme-42"}'>
8</speechify-agent>

Overrides only apply when the widget mints the session itself (public-agent mode, or when you omit session-token). If you pre-mint a session on your backend with session-token + session-url, bake the overrides into that call server-side — the widget has no authority to change an already-issued session and will log a console warning if you combine the two.

Visual

AttributeDescription
avatar-image-urlReplaces the default mic orb with an image (e.g. a bot avatar). Falls back to the mic icon if the image fails to load.
avatar-orb-color-1First gradient stop for the orb background. Any CSS color.
avatar-orb-color-2Second gradient stop for the orb background. Any CSS color.
1<speechify-agent
2 agent-id="a_01HS..."
3 avatar-image-url="https://cdn.example.com/bots/ada.png"
4 avatar-orb-color-1="#6366f1"
5 avatar-orb-color-2="#ec4899">
6</speechify-agent>

Orb colors can also be set from external CSS via the --speechify-agent-orb-1 / --speechify-agent-orb-2 custom properties — see Styling.

Copy

All visible button text is overridable so the widget can speak any language. Each attribute is optional; defaults are in English.

AttributeDefaultWhen shown
start-call-textTalk to agentIdle state, before the call starts.
listening-textListening — tap to endAgent is listening to the user.
speaking-textAgent speaking — tap to endAgent is speaking back.
end-call-textfalls back to listening/speaking defaultsUnified in-call label. Used for listening + speaking when you’d rather show a single “End call” label than per-state copy.
1<speechify-agent
2 agent-id="a_01HS..."
3 start-call-text="Parler au support"
4 listening-text="À l'écoute — touchez pour terminer"
5 speaking-text="L'agent parle — touchez pour terminer">
6</speechify-agent>

Events

The element emits CustomEvents you can listen for with addEventListener:

EventBubblesdetail
statusno"idle", "connecting", "listening", "speaking", "ended", "error"
messageno{ role: "user" | "assistant", text: string, timestamp: number } for each finalised transcript turn
errornoThe underlying Error instance
speechify-agent:callyes (composed){ status: "listening" | "speaking" } — fires once per toggle cycle, the first time the session reaches an in-call status. Useful for analytics: log “user started a call” exactly once per session.
1<speechify-agent id="agent" agent-id="a_01HS..."></speechify-agent>
2<script>
3 const el = document.querySelector("#agent");
4 el.addEventListener("status", (e) => console.log("status:", e.detail));
5 el.addEventListener("message", (e) => console.log(e.detail.role, e.detail.text));
6</script>

Because speechify-agent:call bubbles (with composed: true), you can delegate-listen on an ancestor — handy when the widget is rendered by a framework and you don’t have a stable reference to the element:

1<script>
2 document.addEventListener("speechify-agent:call", (e) => {
3 analytics.track("voice_agent_call_started", { status: e.detail.status });
4 });
5</script>

Programmatic API

For React/Vue/Svelte apps that don’t want the default button UI, import the ESM bundle and call startAgent directly:

1import { startAgent } from "https://api.speechify.ai/v1/widget/agents.mjs";
2
3const handle = await startAgent({
4 agentId: "a_01HS...",
5 onStatus: (s) => console.log("status:", s),
6 onMessage: (m) => console.log(m.role, m.text),
7 onError: (err) => console.error(err),
8});
9
10// Client tools attached to the agent route here:
11handle.registerTool("navigate_to", (args) => {
12 window.location.hash = String(args.section);
13});
14
15await handle.setMicEnabled(false); // mute
16await handle.stop(); // tear down

Styling

The component uses Shadow DOM so your page’s CSS can’t leak in. Light theming is exposed through CSS custom properties on the host:

1speechify-agent {
2 --speechify-agent-bg: #0a0a0a;
3 --speechify-agent-fg: #ffffff;
4 --speechify-agent-accent: #6366f1;
5 --speechify-agent-muted: #71717a;
6 --speechify-agent-radius: 9999px;
7}

Need more control than that? Use the programmatic API and build your own UI on top of the returned AgentHandle.

What’s next