Embed the widget
Install snippet
The widget is a single <script> tag. Drop it on any page,
pass an agent ID, and you're live. The bundle is ≤50KB gzipped, runs in
a Shadow DOM so it can't be styled by your site, and never blocks page
load (it's async).
The snippet
Paste this just before </body>:
<script
src="https://your-app.test/widget/widget.js?v=ab12cd34"
data-agent-id="01HXY..."
async></script>
The full snippet (with your agent ID and the current cache-bust hash) is on every agent's Settings page next to a copy button.
What's in the URL
src— points at/widget/widget.json your Pitchbar deployment. The?v=<hash>suffix is the bundle's content hash; it changes whenever the widget is rebuilt, so customers can't get stuck on stale versions cached by a CDN.data-agent-id— the published agent's ULID. The widget's loader reads this attribute and uses it on every/v1/widget/initcall.async— non-blocking. The widget appears once the bundle finishes downloading; your page's load metrics are unaffected.
What it injects
On boot, the loader:
- Creates a
<div>at the bottom of<body>and attaches a Shadow DOM to it. - Renders the launcher (small button) inside the shadow root.
- Calls
POST /v1/widget/initto get an agent config + JWT + recent history. - Wires up trigger listeners (scroll, idle, exit-intent) per the agent's behavior rules.
- Persists a small
anon_idinlocalStorageso the same browser keeps the same conversation across reloads.
Customizing the launcher
Theme is fully agent-driven — see Persona, theme & prompts.
The widget reads theme.position, theme.primary,
theme.accent, theme.radius, and
theme.launcher_label from the init response and renders
accordingly.
There's no per-page customization — the launcher always reads from the agent. If you need a different look on different pages, embed two different agents.
Programmatic control
The widget exposes a single global, window.Pitchbar.mount(),
used by the loader to boot. The script tag's async
attribute and auto-mount handle the common case for you, so you don't
typically call this directly.
// Mount the widget into a custom host element (rare).
window.Pitchbar.mount(document.getElementById('chat-host'));
There's no public open / close /
send / on API yet — those are deferred. If
you need to fire conversion analytics on lead capture today, subscribe
to the lead.captured webhook instead (see
Outgoing webhooks).
Single-page apps
The widget loads once per page-load, but the conversation persists across in-app navigations as long as the script tag stays in the DOM. You don't need to re-mount it when your router changes routes — the Shadow DOM and JWT survive.
If your SPA fully re-mounts on route changes (e.g. you tear down the body), the widget will re-init and resume the visitor's conversation from the last 24 hours of history.
What gets sent on every init
A POST /v1/widget/init includes:
agent_id— the value fromdata-agent-id.page_url— the currentlocation.href, used for the "current page" boost in retrieval.anon_id— the visitor's persistent ID fromlocalStorage, generated on first visit.
The server also reads the Origin, Referer, and
Accept-Language headers — Origin for the
allowed-origin check,
Accept-Language for default language detection.
Versioning & caching
The bundle URL is content-hashed (?v=<hash>). On every
new deploy, the hash mutates, so Cache-Control: max-age
headers on the bundle can be aggressive (one year) without trapping
customers on an old version.
Customers shouldn't manually pin the hash — always copy the snippet fresh from the agent settings page when re-installing.