Skip to main content
Even without React or a bundler, you can add the widget to any HTML page with one script tag. The IIFE build bundles React internally and exposes one global: window.ProbotSelfHosted.

What you’ll build

  • A static HTML page that mounts the widget via ProbotSelfHosted.mount(el, config).
  • Any backend (Node, Python, Go, Rust, PHP, Cloud Function - whatever) that handles POST /api/probot-chat. The widget doesn’t care what language answers the request; it only needs { reply: "…" } JSON back.

1. HTML markup

mount() takes either a CSS selector string or a real DOM element as its first argument. It returns the React root so you can call .unmount() later if you need to.

2. Backend - any language

The widget calls POST /api/probot-chat with:
And expects:
Any HTTP server that can produce that response works. A few reference implementations:

Node (Express)

Python (FastAPI)

Cloudflare Worker

PHP

Pinning versions

Public CDNs can move fast. Pin the version in the script URL so a future release can’t silently change your widget:
Also consider adding SRI (subresource integrity) once you pin an exact version:
You can generate the integrity hash with curl -s <url> | openssl dgst -sha384 -binary | openssl base64 -A.

Local development

Set up any static-file server plus your backend of choice. Two easy options:
Or the classic Python one-liner for the static side:
The widget’s fetch("/api/probot-chat") will hit whatever origin serves the HTML - point your reverse proxy (nginx, Caddy) or the backend framework itself to answer that route.

Same-origin vs CORS

The example above assumes the HTML page and the /api/probot-chat route are served from the same origin. If they’re on different origins (e.g. the static page is on example.com and the API is on api.example.com), either:
  1. Enable CORS on the backend so it accepts requests from the HTML’s origin, or
  2. Front both with a reverse proxy that presents one origin to the browser.
Option 2 is simpler and avoids preflight requests entirely.

What the widget renders

The IIFE build is entirely self-contained: all styles are injected into the page’s <head> at mount time. That means:
  • No CSS file to load.
  • No conflict with your existing styles (all class names are prefixed probot-*).
  • The FAB is position: fixed; bottom: 20px; right: 20px; by default. To reposition, override .probot-root in your page’s CSS after the script mounts.

Security notes

  • dashboard.token visibility. The token is inline in your page source, so anyone viewing source can copy it. That’s OK - it only grants conversation + lead writes for one bot, and you can revoke it from the dashboard in one click.
  • OPENAI_API_KEY visibility. The LLM API key must NEVER appear in the HTML. Keep it in the backend’s environment variables only. If you see the key in DevTools’ Sources tab, stop and move it to the server.
  • Rate limiting. A publicly-embedded widget = anyone can drain your LLM credits. Add per-IP rate limiting on the backend (see Next.js example § rate limiting for a template).

Unmounting

If you’re on a SPA-style vanilla setup (e.g. htmx or Alpine) and want to tear down the widget on route change: