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

Widget

CDN / Script Tag

Add the ThunderPhone voice widget to any website without a bundler

The CDN build bundles React internally, so you can use the widget on static sites, WordPress, Webflow, or any page where you can add HTML. No npm, no bundler, no framework required.

CDN URLs

Latest (recommended)
<link rel="stylesheet" href="https://cdn.thunderphone.com/widget/latest/style.css" />
<script src="https://cdn.thunderphone.com/widget/latest/widget.js"></script>
Pinned version
<!-- Each stable release also publishes an immutable /widget/vX.Y.Z/ path
     matching the @thunderphone/widget npm version (v1.1.2 at the time of
     writing). If you pin, you own updating the URL for new releases. -->
<link rel="stylesheet" href="https://cdn.thunderphone.com/widget/v1.1.2/style.css" />
<script src="https://cdn.thunderphone.com/widget/v1.1.2/widget.js"></script>

Basic Usage

<!DOCTYPE html>
<html>
<head>
  <link rel="stylesheet" href="https://cdn.thunderphone.com/widget/latest/style.css" />
</head>
<body>
 
  <div id="thunderphone"></div>
 
  <script src="https://cdn.thunderphone.com/widget/latest/widget.js"></script>
  <script>
    ThunderPhone.mount({
      element: '#thunderphone',
      publishableKey: 'pk_live_your_publishable_key',
    })
  </script>
 
</body>
</html>

The widget mounts into the target element and renders as a fixed-position bar in a corner of the viewport (default bottom-right, controlled by the position option). The mount element is only the React root -- its location in the page does not affect where the widget appears.


Mount Options

ThunderPhone.mount() accepts the same options as the React component, plus the element property:

OptionTypeRequiredDefaultDescription
elementstring | HTMLElementYes--CSS selector (e.g., '#thunderphone') or a DOM element reference.
publishableKeystringYes--Publishable API key (pk_live_...). The agent is resolved automatically from the key's widget configuration.
theme'light' | 'dark'No'light'Color scheme.
primaryColorstringNo'#000000' (light) / '#ffffff' (dark)CSS color string used as the accent color.
titlestringNo'Voice assistant'Text displayed in the widget bar.
position'bottom-right' | 'bottom-left' | 'top-right' | 'top-left'No'bottom-right'Fixed viewport position.
apiBasestringNo'https://api.thunderphone.com/v1'API base URL override.
languagestringNo--Per-session language override -- a language code or locale such as en, es, or fr-FR. When unset, the agent's configured language applies.
voicestringNo--Per-session voice override -- a voice name such as maria. When unset, the agent's configured voice applies.
contextstringNo--Per-session factual page or site context passed to the agent. Truncated server-side to 12,000 characters.
onConnect() => voidNo--Called when the voice session connects.
onDisconnect() => voidNo--Called when the session ends.
onError(error) => voidNo--Called on errors. Error has error (code) and message fields.
ringtoneboolean | stringNofalsePlay a ringtone while connecting. true for the default ringtone, or a URL string for custom audio.

Cleanup

ThunderPhone.mount() returns a widget instance with cleanup methods:

<script>
  var widget = ThunderPhone.mount({
    element: '#thunderphone',
    publishableKey: 'pk_live_your_publishable_key',
  })
 
  // Later, when you want to remove the widget:
  widget.unmount()
 
  // Or equivalently:
  widget.destroy()
</script>

Both unmount() and destroy() do the same thing -- they disconnect any active voice session and remove the widget from the DOM. Use whichever reads better in your code.


Examples

Dark Theme with Custom Color

<div id="thunderphone"></div>
 
<link rel="stylesheet" href="https://cdn.thunderphone.com/widget/latest/style.css" />
<script src="https://cdn.thunderphone.com/widget/latest/widget.js"></script>
<script>
  ThunderPhone.mount({
    element: '#thunderphone',
    publishableKey: 'pk_live_your_publishable_key',
    theme: 'dark',
    primaryColor: '#8b5cf6',
    title: 'Talk to our AI',
  })
</script>

Custom Position

<div id="thunderphone"></div>
 
<script src="https://cdn.thunderphone.com/widget/latest/widget.js"></script>
<script>
  ThunderPhone.mount({
    element: '#thunderphone',
    publishableKey: 'pk_live_your_publishable_key',
    position: 'bottom-left',
  })
</script>

With Event Callbacks

<div id="thunderphone"></div>
 
<link rel="stylesheet" href="https://cdn.thunderphone.com/widget/latest/style.css" />
<script src="https://cdn.thunderphone.com/widget/latest/widget.js"></script>
<script>
  ThunderPhone.mount({
    element: '#thunderphone',
    publishableKey: 'pk_live_your_publishable_key',
    onConnect: function () {
      console.log('Call started')
    },
    onDisconnect: function () {
      console.log('Call ended')
    },
    onError: function (error) {
      console.error('Widget error:', error.error, error.message)
    },
  })
</script>

With Ringtone

<div id="thunderphone"></div>
 
<link rel="stylesheet" href="https://cdn.thunderphone.com/widget/latest/style.css" />
<script src="https://cdn.thunderphone.com/widget/latest/widget.js"></script>
<script>
  ThunderPhone.mount({
    element: '#thunderphone',
    publishableKey: 'pk_live_your_publishable_key',
    ringtone: true, // or a custom URL: 'https://example.com/ringtone.mp3'
  })
</script>

Using a DOM Element Reference

<div id="thunderphone"></div>
 
<script src="https://cdn.thunderphone.com/widget/latest/widget.js"></script>
<script>
  var container = document.getElementById('thunderphone')
 
  ThunderPhone.mount({
    element: container,
    publishableKey: 'pk_live_your_publishable_key',
  })
</script>

WordPress / CMS Integration

Add this to a Custom HTML block or your theme's footer:

<link rel="stylesheet" href="https://cdn.thunderphone.com/widget/latest/style.css" />
<script src="https://cdn.thunderphone.com/widget/latest/widget.js"></script>
 
<div id="thunderphone-widget"></div>
<script>
  ThunderPhone.mount({
    element: '#thunderphone-widget',
    publishableKey: 'pk_live_your_publishable_key',
  })
</script>

Troubleshooting

Widget doesn't appear

Make sure both the CSS and JS files are loaded. Check the browser console for network errors. Verify that the target element exists in the DOM before calling ThunderPhone.mount().

ThunderPhone is not defined

The script has not loaded yet. Ensure the <script> tag for widget.js appears before your mount call, or wrap the mount call in a DOMContentLoaded listener.

Domain not allowed error

Add your domain to the allowed domains list in Developers settings at app.thunderphone.com. Remember that localhost is always allowed.


Next Steps