The Shopbell Voice Widget is a JavaScript file you embed on any website. It exposes <code>window.ShopbellWidget</code> as the public API. Visitors click a button on your site, talk through their browser microphone, and the agent answers — same agent, same workflow, same compliance rules as your phone line. No phone number needed. This guide shows the embed snippet, the API surface, and React + vanilla HTML examples.

The basic embed snippet

From your workflow's Settings page (click a workflow then Settings), Shopbell generates a one-line embed snippet. Paste it anywhere on your site — typically in the <head> or before the closing </body> tag. It looks like:

<!-- Shopbell Voice Widget -->\n<script>\n  (function(d, s, id) {\n    var js, fjs = d.getElementsByTagName(s)[0];\n    if (d.getElementById(id)) return;\n    js = d.createElement(s); js.id = id;\n    js.src = 'https://app.shopbell.ai/embed/dograh-widget.js?token=YOUR_TOKEN&environment=production&apiEndpoint=https://app.shopbell.ai';\n    js.async = true;\n    fjs.parentNode.insertBefore(js, fjs);\n  }(document, 'script', 'shopbell-widget'));\n</script>

(Note: the file is still named dograh-widget.js for backwards compatibility — but the exposed API is window.ShopbellWidget, not DograhWidget.)

The window.ShopbellWidget API surface

After the script loads, window.ShopbellWidget becomes available. The API:

window.ShopbellWidget.start() — starts a call. Triggers the Web Call flow. Connects audio, the agent speaks the Start Call greeting. Returns a Promise<void>.

window.ShopbellWidget.end() — ends the current call. Hangs up the audio, cleans up the WebRTC connection. Returns a Promise<void>.

window.ShopbellWidget.onCallStart(callback) — register a callback fired when the call connects (after greeting). Useful for analytics events, UI state changes.

window.ShopbellWidget.onCallEnd(callback) — register a callback fired when the call ends (caller hangs up, agent hangs up, or you call end()).

window.ShopbellWidget.onStatusChange(callback) — register a callback fired every time call status changes. Receives one of: 'idle', 'connecting', 'connected', 'failed'. Use this for UI state updates (show busy UI while connecting, toggle call/hang-up button while connected).

window.ShopbellWidget.onReady(callback) — register a callback fired when the widget script has fully initialized and the API is ready to call. Use this to make sure you don't call start() before the widget is ready.

window.ShopbellWidget.onError(callback) — register a callback fired when an error occurs (network failure, token expired, browser mic blocked). Receives an error message string.

Vanilla HTML example — full working page

<!DOCTYPE html>\n<html>\n<body>\n  <button id="startBtn">Start Call</button>\n  <p id="status">Click Start to begin.</p>\n\n  <!-- Shopbell Voice Widget -->\n  <script>\n    (function(d, s, id) {\n      var js, fjs = d.getElementsByTagName(s)[0];\n      if (d.getElementById(id)) return;\n      js = d.createElement(s); js.id = id;\n      js.src = 'https://app.shopbell.ai/embed/dograh-widget.js?token=YOUR_TOKEN&environment=production&apiEndpoint=https://app.shopbell.ai';\n      js.async = true;\n      fjs.parentNode.insertBefore(js, fjs);\n    }(document, 'script', 'shopbell-widget'));\n  </script>\n\n  <script>\n    window.ShopbellWidget?.onReady(() => {\n      document.getElementById('startBtn').addEventListener('click', () => {\n        window.ShopbellWidget.start();\n      });\n    });\n\n    window.ShopbellWidget?.onStatusChange((status) => {\n      document.getElementById('status').textContent = status;\n      const btn = document.getElementById('startBtn');\n      if (status === 'connected' || status === 'connecting') {\n        btn.textContent = 'End Call';\n        btn.onclick = () => window.ShopbellWidget.end();\n      } else {\n        btn.textContent = 'Start Call';\n        btn.onClick = () => window.ShopbellWidget.start();\n      }\n    });\n  </script>\n</body>\n</html>

React example — call state with hooks

import { useEffect, useState } from 'react';\n\nexport function ShopbellAgent() {\n  const [isCallActive, setIsCallActive] = useState(false);\n  const [status, setStatus] = useState('idle');\n\n  useEffect(() => {\n    // Widget auto-initializes when the script loads (script loaded by embed snippet)\n    window.ShopbellWidget?.onCallStart(() => {\n      setIsCallActive(true);\n    });\n    window.ShopbellWidget?.onCallEnd(() => {\n      setIsCallActive(false);\n    });\n    window.ShopbellWidget?.onStatusChange(setStatus);\n  }, []);\n\n  return (\n    <div>\n      <p>Call status: {status}</p>\n      <button\n        onClick={() => isCallActive ? window.ShopbellWidget.end() : window.ShopbellWidget.start()}\n        disabled={status === 'connecting'}\n      >\n        {isCallActive ? 'End Call' : 'Start Call'}\n      </button>\n    </div>\n  );\n}

Token generation and security

The embed snippet includes a token in the URL query string. This token is generated per-workflow — one workflow, one token. Rotate tokens any time from the workflow's Settings page (click 'Rotate Token'). Old tokens stop working immediately. The token is domain-restricted: only the domains you whitelist in Settings can use the embed — anyone stealing your token and embedding on a different domain gets blocked.

Optional: set an expiration date on the token (default 30 days, max 365 days). After expiration, the script returns 401 and the widget initial start() call fails — refresh the embed snippet from your Settings page and replace the old one on your site.

Frequently Asked Questions

Do I need a phone number to use the website widget?

No. The widget uses Web Calls — browser microphone to your agent's API directly. It does NOT use your Twilio number, does NOT incur Twilio per-minute charges. Same agent, same workflow, different transport.

Can I see who used the widget on my site?

Yes. Every Web Call via the widget creates a recording entry in your Recordings page, just like a phone call. The visitor's IP address is captured, no phone number. Sort by 'web' vs 'phone' in the Recordings filter.

What if my visitor's browser blocks the microphone?

The widget's onError callback fires with 'Microphone permission denied'. Show the visitor a message like 'Please allow microphone access in your browser settings to use the voice call feature.' Most modern browsers show a one-time prompt — if the user clicked Block once, they need to undo it in browser site permissions.

Can I customize the widget UI?

The basic embed snippet is 'headless' — you build your own button and connect start()/end() calls to it. Want a floating chat-bubble widget with built-in UI? Contact us — we have an enhanced UI widget available on the Done-For-You plan.