Install the chat widget in one line of HTML.
One script tag. No framework. No backend changes on your side. Paste before </body> and a chat bubble appears in your brand color, wired to your VerboSync tenant.
<script async src="https://YOUR-VERBOSYNC-HOST/api/widget/widget.js?t=YOUR_TENANT_ID"></script> Replace the two placeholders, paste before </body>, and the bubble appears.
Copy & paste
<!-- VerboSync chat widget -->
<script
async
src="https://gateway-production-8b00.up.railway.app/api/widget/widget.js?t=00000000-0000-0000-0000-000000000000"
></script> Two things to change:
| Placeholder | What to put | Example |
|---|---|---|
hosthttps://gateway-…railway.app | Your VerboSync gateway URL — given to you by VerboSync | https://gateway-production-8b00.up.railway.app or your custom domain |
?t= | Your VerboSync tenant ID (UUID) | cc7dfcde-3f6e-4727-96d5-46fd57c83089 |
How it works (in 30 seconds)
- The browser loads
widget.jsfrom your VerboSync host. The script reads?t=off its own<script src>to learn which tenant it belongs to. - It calls
GET /api/widget/config?tenant_id=…to fetch your branding (color, greeting, position, avatar). - When a visitor clicks the bubble, it POSTs
/api/widget/sessionto mint a short-lived JWT and opens a WebSocket atwss://…/ws/chat?token=…. - Messages flow over that WebSocket. Replies stream back into the panel.
The whole UI lives inside a closed Shadow DOM — its styles can't leak into your page and your CSS can't break the widget.
Where to find your tenant ID
- From your VerboSync admin — they can copy it from the operator console.
- From any
/api/*request — browser devtools → Network → look at theX-Tenant-IDrequest header. - Ask support if the operator console doesn't expose it yet.
The tenant ID is a public identifier, not a secret. It's safe to put in page HTML, source control, and analytics. Authentication happens server-side via the short-lived JWT session token.
Where to paste the tag
| Site type | Where to paste |
|---|---|
| Plain HTML | Just before </body> |
| WordPress | Appearance → Theme File Editor → footer.php (before </body>) — or a plugin like Insert Headers and Footers |
| Shopify | Online Store → Themes → Edit code → theme.liquid (before </body>) |
| Webflow | Project Settings → Custom Code → Footer Code |
| Squarespace | Settings → Advanced → Code Injection → Footer |
| Wix | Site → Custom Code → add new → Place code in Body — end on All pages |
| Google Tag Manager | New tag → Custom HTML → paste the snippet → trigger: All Pages |
| React / Next.js / Vite SPA | Add to your root HTML once (index.html or app/layout.tsx) — NOT per-route. See SPA section. |
The tag has async, so it loads in parallel with your page and never blocks rendering.
SPA frameworks (React, Next.js, Vue)
Mount the script once at app shell level, not inside route components, so re-renders don't add the bubble multiple times.
Next.js (App Router)
// app/layout.tsx
import Script from "next/script";
export default function RootLayout({ children }) {
return (
<html>
<body>
{children}
<Script
src="https://YOUR-HOST/api/widget/widget.js?t=YOUR_TENANT_ID"
strategy="afterInteractive"
/>
</body>
</html>
);
} Next.js (Pages Router)
// pages/_app.tsx
import Script from "next/script";
export default function App({ Component, pageProps }) {
return (
<>
<Component {...pageProps} />
<Script
src="https://YOUR-HOST/api/widget/widget.js?t=YOUR_TENANT_ID"
strategy="afterInteractive"
/>
</>
);
} Plain Vite / CRA / Vue / Svelte SPA
Put the <script> tag in index.html before </body> like a static-HTML site. Don't add it via useEffect or a component — once is enough.
Customizing the look
You do not configure the widget in code. All appearance settings live in the VerboSync operator console so non-developers can change them without redeploying your site:
| Setting | What it controls | Default |
|---|---|---|
| Brand color | Bubble background, header, send button, visitor messages | #4F46E5 |
| Greeting message | First message shown when the panel opens | Hi! How can we help you today? |
| Position | bottom-right or bottom-left | bottom-right |
| Avatar URL | Header avatar image (~36×36 px, any size accepted) | Default icon |
| Enabled | Master kill switch — turn off without removing the script tag | On |
Tell your VerboSync admin which knobs you want changed; they edit them in the operator console and the widget picks up the new config on next page load (cached ~1 hour by HTTP cache headers on widget.js).
Verifying the install
- Load any page where you pasted the tag.
- You should see a circular chat bubble in your brand color (bottom-right by default).
- Click it — the panel slides up with your greeting.
- Type a message and press Enter — it should appear on the right; the agent should "type…" and reply.
If nothing appears
| What you see | Likely cause | Fix |
|---|---|---|
| No bubble at all, console silent | Script tag missing/typo'd; widget.js returned 404 | Re-check <script src> and the host |
Console: Missing tenant ID in script tag (?t=TENANT_ID) | Forgot ?t=… | Add it |
Console: Failed to initialize widget … 404 | Tenant ID wrong, OR widget disabled, OR Chat SKU not active | Verify the ID; have your VerboSync admin check Settings → Widget + Billing → Chat |
Console: 402 / EntitlementError | Chat add-on isn't on this tenant | Add the Chat SKU in the operator console |
| Bubble appears, but panel never opens / "trouble connecting" | WebSocket blocked (corporate firewall, CSP) | See CSP section |
| Bubble appears twice | Tag included twice (e.g. both index.html and a React useEffect) | Keep only one |
Security model
| Concern | Answer |
|---|---|
| Is the tenant ID a secret? | No. It's a public identifier, like a Stripe publishable key. Authentication happens via the short-lived JWT minted at /api/widget/session. |
| What authenticates the WebSocket? | A per-session JWT (HS256, signed server-side) carried as ?token=. Encodes tenant_id, session_id, and an expiry. |
| Can someone else embed our widget on their site? | Today, yes — origin whitelisting is on the roadmap. If you need it, ask support. |
| Cookies? | None. |
localStorage? | None. |
| Fingerprinting? | No client-side fingerprinting. The HTTP request reaches the VerboSync gateway, which sees the visitor's IP (standard for any HTTP call). |
| GDPR-friendly? | Only collects what the visitor types and standard request metadata. Treat it like any other live-chat service in your privacy policy. |
Style isolation: the widget mounts under a closed Shadow DOM. Your CSS cannot reach into it, and its CSS cannot leak into your site — so you cannot override widget styles from your CSS. Use the operator-console config knobs instead.
Content Security Policy (CSP)
If your site has a strict CSP, you need three allowances. Replace YOUR-HOST with your VerboSync gateway:
script-src 'self' https://YOUR-HOST;
connect-src 'self' https://YOUR-HOST wss://YOUR-HOST;
img-src 'self' data: https://YOUR-HOST; If you use script-src 'strict-dynamic', the async script will still load (added to the DOM, not inline), but make sure your nonce/hash strategy isn't applied to third-party scripts. The widget uses Shadow DOM and does not need style-src 'unsafe-inline'.
Removing or disabling
- Toggle off in the operator console (Settings → Widget → Enabled). The script tag can stay; the widget will render nothing.
- Drop the Chat SKU — same effect, plus the WebSocket can't be opened even with a cached bundle.
- Remove the
<script>tag from your site.
Option 1 is reversible without touching code — use it for maintenance windows.
Caching & versioning
widget.js is served with Cache-Control: public, max-age=3600 — visitors may cache it for an hour. Config (/api/widget/config) is not cached, so brand-color and greeting changes apply immediately. To force an update before the hour is up, version-bust the URL:
<script async src="https://YOUR-HOST/api/widget/widget.js?t=YOUR_TENANT_ID&v=2"></script> Reference — what the widget calls
| Method | URL | Purpose |
|---|---|---|
GET | /api/widget/widget.js?t=TENANT_ID | The bundle |
GET | /api/widget/config?tenant_id=TENANT_ID | Branding / enable flag |
POST | /api/widget/session (body: { tenant_id }) | Mints a short-lived JWT |
WS | /ws/chat?token=JWT | Bidirectional chat ({ type:"message"|"reply"|"ack", text }) |
All four are served by your VerboSync gateway. The widget never talks to anyone else — no third-party CDN, no analytics pings.
Install checklist
- Got your tenant ID (UUID) and gateway host from VerboSync.
- The Chat SKU is active on your VerboSync tenant.
- Pasted the
<script> tag before </body> on every page (or once at SPA shell level). - Reloaded a page — bubble appears in your brand color.
- Clicked the bubble — greeting appears.
- Sent a test message — got a reply.
- Verified an operator can see the conversation in the operator console (Conversations inbox, channel = web).
- If you have a CSP, added the three allowances above.
- If your site has cookie/consent gating, decided whether the widget loads before or after consent.
FAQ
Can I embed the widget on multiple sites for the same tenant?
Yes — paste the same tag on as many sites as you like. Conversations from all of them land in one inbox.
Can different pages show different greetings?
Not today — greeting is per-tenant. Visitor-context APIs are on the roadmap; ask support.
Does the widget work on mobile?
Yes. The panel is responsive (max-width: calc(100vw - 32px)) and uses viewport-aware sizing.
Can I host widget.js from my own domain?
No — the bundle hard-codes the gateway origin it was served from for its config/session/WebSocket calls. Load it from your VerboSync host.
Will it slow down my site?
The script tag is async, so it doesn't block the parser. The bundle is small (single self-contained file, no framework) and the network calls only happen when a visitor opens the panel.