Webble

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.

app/api/webble-token/route.ts
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-url is the endpoint from step 2, on your own origin.
  • data-wb-lang is optional. It forces the widget's language instead of following the visitor's browser.
  • data-wb-pages is 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/** /docs and 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.

On this page