Embed snippet
The complete embed is two pieces of HTML:
<powsoo-voice widget-id="wid_YOUR_ID_HERE"></powsoo-voice><script> (() => { const d = document, s = d.createElement('script'); s.type = 'module'; s.src = 'https://w.powsoo.com/widget.js'; d.head.append(s); })();</script>Place these anywhere in <body>. That is the entire integration.
How it loads
- The IIFE creates a
<script type="module">tag pointing towidget.json Powsoo’s CDN (Cloudflare R2 + CDN). widget.jsregisters the<powsoo-voice>custom element — a Lit web component with shadow DOM.- On
connectedCallbackthe element readswidget-id, fetches its config from the API, and renders a call button. - When the user clicks, the widget mints a voice token and starts the Retell/Vapi WebRTC call.
Widget config (theme, mode, agent metadata) is fetched once on first load and cached client-side for ~30 minutes. Subsequent page loads within that window skip the config fetch.
Attributes
| Attribute | Required | Type | Description |
|---|---|---|---|
widget-id | ✓ | string | The wid_* identifier from the Powsoo dashboard. Public — contains no credentials. |
Placement and positioning
The <powsoo-voice> element renders inline by default. To pin it to a corner of the screen:
<powsoo-voice widget-id="wid_abc123" style="position: fixed; bottom: 24px; right: 24px; z-index: 999;"></powsoo-voice>The widget’s shadow DOM fully isolates its styles from the host page — host page CSS cannot bleed in and widget CSS cannot bleed out.
Security model
widget-id is public — safe to commit to source, include in client HTML, and deploy to a CDN. It contains no credentials.
Access control is server-side: the Powsoo API validates the Origin header on every request against the widget’s Allowed origins list, configured in the dashboard. A mismatch is rejected before any token is minted. See Security for the full model.
Next: Framework guides
See Framework guides for placement instructions for React, Next.js, Vue, Webflow, WordPress, and others.