ThunderPhone 2.0 is live.Self-serve, from 2¢/min.Read the announcement

Developer cookbook

Embed the web widget

Drop a voice agent onto your marketing or support site with a single script tag — visitors talk to it straight from the browser, no phone number required.

The web widget gives your site visitors a click-to-talk conversation with an AI agent, using the browser's microphone. It's a separate JavaScript / React SDK with its own SDK reference — this guide focuses on the ThunderPhone-side setup the widget needs.

Prerequisites

  1. Create an agent

    The agent whose prompt and voice will run the widget session. Set widget_enabled: true (the default).

  2. Decide the routing mode
    • mode="agent" — one static agent per key. Simplest.
    • mode="webhook" — your server picks the agent per visitor via a web.incoming webhook. Use this for logged-in users, A/B tests, or per-page routing.
  3. List the allowed domains

    Name every hostname that will embed the widget. Browsers send the page's origin with each session request, so the list stops your key from working when someone copies it onto another site. It is not authentication: a script outside a browser can send any Origin header. localhost is not allowed automatically in production; to test locally against the production API, add localhost to the list.

  4. Choose the browser overrides

    Decide which session settings your page may send. New keys accept none; see Browser overrides.

Create a publishable key

Static agent
curl -X POST https://api.thunderphone.com/v1/publishable-key \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name":            "Marketing site (prod)",
    "mode":            "agent",
    "agent_id":        12,
    "allowed_domains": ["example.com", "*.example.com"],
    "allowed_overrides": ["context"]
  }'
Dynamic via webhook
curl -X POST https://api.thunderphone.com/v1/publishable-key \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name":            "Support (dynamic)",
    "mode":            "webhook",
    "webhook_url":     "https://example.com/thunderphone/widget-hook",
    "allowed_domains": ["support.example.com"]
  }'

The response includes a key starting with pk_live_.... Publishable keys are public by design — safe to ship in your front-end bundle. See Publishable keys reference for all fields.

Drop the widget on your site

Three integration options are covered in the widget SDK docs:

All three accept the same publishableKey and render the mic button plus the in-call audio element.

Browser overrides

The widget can send four per-session settings from the page:

SettingWidget propWhat it changes
contextcontextText from the page, appended to the agent prompt as reference
variablesnone (raw POST /v1/widget/session only)Values for {{variable}} placeholders in the prompt
voicevoiceThe agent's voice for this session
languagelanguageThe language the session starts in

Your publishable key is public, so anyone who has it can send these settings, including from a script that fakes your domain. Each one is therefore off until you allow it on the key: tick it under Browser overrides when you create or edit the widget in the dashboard, or set allowed_overrides through the Publishable keys API. Allow only what your page actually sends.

A session request that carries a setting the key does not allow fails with 400 and names it:

{
  "error": "override_not_allowed",
  "fields": ["voice"],
  "message": "This publishable key does not allow the browser to set `voice`. ..."
}

The check applies in both modes. In webhook mode an allowed setting is also forwarded to your web.incoming webhook (context arrives as website_context). Keys created before this setting existed allow all four, so existing embeds keep working; turn off what you don't use.

Widget context is truncated at 12,000 characters (roughly 3,400 tokens of typical English text) and counts toward the prompt-size surcharge. The agent is told to treat it as page content, not as instructions.

Widget-mode webhooks

When mode="webhook", ThunderPhone calls your webhook_url on every session start with a web.incoming payload. Return the agent configuration you want to run for that visitor — it follows the same response schema as phone calls:

{
  "prompt":  "You are a VIP concierge for Jane Doe.",
  "voice":   "john",
  "product": "storm-base",
  "tools":   [ /* per-customer tools */ ]
}

You can mix context from your own session (which customer is browsing, which page they're on) into the prompt, and swap agents per rollout.

Observe the sessions

Widget sessions show up in GET /v1/calls with direction="widget" — same transcript, recording, grading, and billing as phone calls. Filter by direction to build a widget-only dashboard.


Next steps