Skip to main content

Overview

The Website Chat Widget lets you embed a Spinnable worker directly on your website as a floating chat window. Visitors can ask questions, get support, or interact with your worker — all without leaving your site.
The widget is configured from your worker’s Details → Website Widget section in the Spinnable dashboard. This guide covers the implementation details for embedding and customizing the widget on your website.

Quick Start

1

Enable the widget

In your worker’s dashboard, go to Details → Website Widget, create a widget key, and configure your allowed domains.
2

Add the embed code

Copy the embed snippet from the dashboard and paste it into your website’s HTML, just before the closing </body> tag:
<script
  src="https://app.spinnable.com/widget/v1/embed.js"
  data-api-url="https://api.spinnable.com"
  defer
></script>
<spinnable-widget key="wk_xxxxx"></spinnable-widget>
Replace wk_xxxxx with the widget key from your dashboard.
3

You're live!

The widget appears as a floating chat button in the bottom-right corner of your page. Visitors can click it to start chatting with your worker.
Make sure your website’s domain is added to the Allowed Domains list in the widget settings. The widget will not work on domains that aren’t explicitly allowed.

Visitor Identification

By default, the widget assigns each browser a random anonymous ID stored in localStorage. This means visitors get a fresh identity per device/browser and their sessions are not linked across devices. To provide a better experience — like linking conversations to your own user accounts and letting visitors see their past sessions — you can identify visitors using SpinnableWidget.init().
When no visitor identification is provided, the widget automatically:
  • Generates a unique ID per browser and stores it in localStorage
  • Sessions are tied to that browser only
  • If the visitor clears their browser data, their history is lost
No code changes needed — this is the default behavior.

About the init polyfill

You’ll notice the init snippet includes a small “polyfill” block before the widget script:
<script>
  window.SpinnableWidget = window.SpinnableWidget || {};
  window.SpinnableWidget.init = window.SpinnableWidget.init || function(opts) {
    window._spinnableWidgetInit = opts;
  };
</script>
This ensures SpinnableWidget.init() can be called before the widget script finishes loading. The widget script loads with defer, so this polyfill safely stores your configuration until the widget is ready to pick it up. You can place SpinnableWidget.init() anywhere on the page — before or after the widget script.

Session Management

The widget supports multiple conversation sessions per visitor:
  • New visitors see an intro panel with a welcome message and suggested prompts (configured in the dashboard)
  • Returning visitors (identified via visitorId) see a list of their past sessions and can:
    • Open any previous conversation to continue it
    • Start a new conversation with the New Chat button
  • The current session is persisted in localStorage and survives page refreshes

Theming & Customization

Via the Dashboard

The primary way to customize the widget’s appearance is through the Styling tab in Details → Website Widget. You can configure:
  • Panel title — the header text shown at the top of the widget
  • Theme colors — primary, background, foreground, and text colors
  • Intro panel — heading, description, and suggested prompt buttons
  • Input placeholder — the placeholder text in the message input
  • Launcher icon — choose between chat, help, or support icons
  • Launcher positionbottom-right or bottom-left

Via JavaScript (Per-Page Overrides)

For advanced use cases — like matching different themes on different pages — you can override the dashboard config by passing a theme object to SpinnableWidget.init():
<script>
  SpinnableWidget.init({
    visitorId: "user-12345",
    theme: {
      primary: "#0066cc",
      primaryForeground: "#ffffff",
      background: "#f8fafc",
      foreground: "#1e293b"
    }
  });
</script>
JavaScript overrides take priority over dashboard settings. This lets you use the dashboard as a default while customizing specific pages as needed.
PropertyDescriptionDefault
primaryButtons, header, user message bubbles, launcherEmerald green
primaryForegroundText on primary-colored elementsWhite
backgroundWidget panel backgroundWhite
foregroundMain text colorDark gray
borderBorders and dividersLight gray
mutedMuted UI elementsSlate
mutedForegroundSecondary text (empty states, descriptions)Slate
errorBgError state backgroundLight red
errorTextError state textDark red
fontFamilyFont stackInter, system-ui, sans-serif
borderRadiusPanel corner radius12px
borderRadiusSmInput and button corner radius8px

Allowed Domains & Security

The widget uses your Widget Key and Origin validation to ensure only authorized websites can embed your worker:
  • Every request from the widget includes the X-Widget-Key header and the browser’s Origin header
  • The backend validates both against your configuration
  • Wildcard domains are supported (e.g., https://*.acme.com matches all subdomains)
If you see a 403 error in the widget, your website’s domain is not in the allowed list. Add it in Details → Website Widget → Allowed Domains.

Key Regeneration

If you suspect your widget key has been compromised, you can regenerate it from the dashboard:
  • A new key is issued immediately
  • The old key remains valid for 24 hours (grace period) so you have time to update your embed code
  • After 24 hours, the old key stops working

Action Modes

When creating a widget key, you choose an action mode that controls what your worker can do when responding to widget visitors:
ModeDescription
Guest (recommended)Read-only interactions — safest for public websites
CollaboratorLimited access — can read and contribute but not manage
Org MemberCan read and contribute with broader permissions
OwnerFull access — use with caution on public sites
For public-facing websites, we strongly recommend using Guest mode to minimize risk. Only use higher permission levels if your widget is on an authenticated internal tool.

File Uploads

The widget supports file uploads from visitors:
  • Maximum 3 files per message
  • Maximum 5 MB per file
  • Drag-and-drop is supported

Troubleshooting

Your website’s domain is not in the allowed domains list. Go to Details → Website Widget → Allowed Domains and add your domain (e.g., https://example.com). Wildcard patterns like https://*.example.com are also supported.
The widget key is invalid or has been revoked. Check your key in the dashboard — if it was regenerated more than 24 hours ago, the old key has expired. Update your embed code with the new key.
Verify the following:
  • The embed code is placed before the closing </body> tag
  • The key attribute on <spinnable-widget> matches your dashboard key
  • The script src URL is correct and accessible
  • Check your browser’s developer console for errors
If you’re using the default anonymous mode, sessions are tied to localStorage — they only exist in that specific browser. For cross-device persistence, implement Visitor Identification with a consistent visitorId from your user system.
A few things to check:
  • Dashboard theme changes apply on the next widget load — refresh the page
  • JavaScript overrides via SpinnableWidget.init() take priority over dashboard settings
  • Ensure color values are valid CSS (hex, HSL, or named colors)

Full Example

Here’s a complete implementation with visitor identification, profile data, and custom theming:
<!-- Polyfill: allows SpinnableWidget.init() to be called before the script loads -->
<script>
  window.SpinnableWidget = window.SpinnableWidget || {};
  window.SpinnableWidget.init = window.SpinnableWidget.init || function(opts) {
    window._spinnableWidgetInit = opts;
  };
</script>

<!-- Widget script -->
<script
  src="https://app.spinnable.com/widget/v1/embed.js"
  data-api-url="https://api.spinnable.com"
  defer
></script>

<!-- Widget element -->
<spinnable-widget key="wk_xxxxx"></spinnable-widget>

<!-- Initialize with visitor data and theme -->
<script>
  SpinnableWidget.init({
    visitorId: "user-12345",
    visitor: {
      name: "Jane Smith",
      email: "jane@example.com",
      phone_number: "+1234567890"
    },
    theme: {
      primary: "#6366f1",
      primaryForeground: "#ffffff",
      background: "#ffffff",
      foreground: "#1e293b"
    }
  });
</script>

Next Steps