Installing the widget
A small token endpoint on your backend and one script tag on your pages.
Installing Webble takes two pieces: a small token endpoint on your backend and a script tag on your pages. The dashboard's Install widget dialog generates both with your site's values already filled in. This page explains what they do — pick your stack in the tabs below.
Why a token endpoint?
The widget never ships a secret to the browser. Your backend holds a Webble API token and exchanges it for short-lived chat tokens scoped to your website. The widget polls your endpoint and uses whatever it returns. A leaked chat token expires on its own, and revoking the API token stops the exchange.
1. Create an API token
Create an API token for your website in the dashboard and store it in your
backend's secrets as WEBBLE_API_TOKEN. It must never appear in client-side
code. One token can cover several of your websites; the websiteId you send
in the exchange below says which one the chat token is for.
2. Add the token endpoint
Your endpoint forwards the exchange to Webble and returns the response
unchanged — status code included, because the widget reads a 403 (test mode,
spend cap) as "hide yourself". Mark the response Cache-Control: no-store,
otherwise a cached response keeps serving a token that has already expired.
Replace YOUR_SITE_ID with your website's id, shown in the dashboard.
export async function GET() {
const res = await fetch("https://webble.it/api/v1/widget-tokens", {
method: "POST",
headers: {
authorization: `Bearer ${process.env.WEBBLE_API_TOKEN}`,
"content-type": "application/json",
},
body: JSON.stringify({ websiteId: "YOUR_SITE_ID" }),
cache: "no-store",
});
return Response.json(await res.json(), {
status: res.status,
headers: { "cache-control": "no-store" },
});
}3. Add the script tag
data-wb-token-url points at the endpoint from step 2, on your own origin. defer keeps the
tag from blocking your page's rendering, so it can sit in <head> or at the
end of <body> alike.
<script
defer
src="https://webble.it/widget/webble.iife.js"
data-wb-token-url="/api/webble-token"
></script>data-wb-token-urlis the endpoint from step 2, on your own origin.data-wb-langis optional. It forces the widget's language instead of following the visitor's browser.data-wb-pagesis optional. It lists the pages the widget belongs on, as path patterns separated by spaces or commas; everywhere else it stays off, even when your site navigates without reloading./is the homepage alone,/docs/*one level under/docs,/docs/**/docsand everything below it, and a leading!excludes:data-wb-pages="/** !/checkout/**"shows the widget everywhere but the checkout. Without it the widget is on every page the script tag is.
The widget renders in a Shadow DOM, so your site's CSS and the widget's never touch. Colors, launcher style and the intro message are configured per website in the dashboard's appearance studio.
Signed-in visitors
If your site has its own login, tell Webble who is chatting. Your endpoint
sends a visitorId in the request that mints the chat token: a stable
identifier of the signed-in visitor that is not a secret and does not reveal
who they are, such as an HMAC of their user id with a key of your own, or
null for an anonymous visitor (at most 256 characters). Only your server can
vouch for it, because only your server sees your login session and holds the
API token.
const session = await getSession(request);
const res = await fetch("https://webble.it/api/v1/widget-tokens", {
method: "POST",
headers: {
authorization: `Bearer ${process.env.WEBBLE_API_TOKEN}`,
"content-type": "application/json",
},
body: JSON.stringify({
websiteId: "YOUR_SITE_ID",
visitorId: session ? hmac(session.userId) : null,
}),
});
const body = await res.json();
return Response.json(
{
...body,
guestToken: session ? await mintGuestToken(session) : null,
},
{ status: res.status, headers: { "cache-control": "no-store" } }
);Webble records the visitorId with the chat token and does not send it back.
Each chat message the widget sends carries only the chat token, and Webble
reads the visitor from it, so a page cannot change who it chats as.
If your website's MCP servers need to act as the visitor, add a guestToken
to the JSON your endpoint returns too: any string that identifies the
signed-in visitor to your own API, or null for an anonymous one. The widget
sends it with every chat message, and Webble forwards it to every MCP server
enabled for the website in the X-Webble-Guest-Token header. Webble never
stores it. It is refreshed together with the chat token, so a visitor who
signs in or out is picked up at the next refresh. The guest token must stay
valid for at least 15 minutes, the life of a chat token.
Test mode vs live
While your website is in test mode, the token exchange refuses deployed widgets, and only the dashboard preview can chat. Switch the website to live mode when you're ready for visitors.