D Dronggo docs

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.js on 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/init call.
  • async — non-blocking. The widget appears once the bundle finishes downloading; your page's load metrics are unaffected.
  • data-locale (optional) — force the widget's language (e.g. data-locale="nl"). Omit it and the widget follows your page's <html lang> automatically. See Language below.

Language

The widget renders its UI — launcher label, starter chips, buttons, and system messages — in the visitor's language, resolved at init in this order:

  1. data-locale on the script tag, if set — an explicit override (e.g. data-locale="de").
  2. The host page's <html lang> — so the widget matches whatever language your site is currently rendering. On our own marketing site this is what makes the demo follow the language switcher.
  3. The agent's default language (Settings → the agent's language) when the page declares none.
  4. The browser's Accept-Language, then English.

Regional tags degrade gracefully: en-GBen, pt-BR stays pt-BR when that file ships. The page language wins over the agent default, so a single agent embedded on a multilingual site speaks each page's language. Translate the launcher label and starter chips per language in Translations; anything without a translation falls back to the original text.

What it injects

On boot, the loader:

  1. Creates a <div> at the bottom of <body> and attaches a Shadow DOM to it.
  2. Renders the launcher (small button) inside the shadow root.
  3. Calls POST /v1/widget/init to get an agent config + JWT + recent history.
  4. Wires up trigger listeners (scroll, idle, exit-intent) per the agent's behavior rules.
  5. Persists a small anon_id in localStorage so 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 from data-agent-id.
  • page_url — the current location.href, used for the "current page" boost in retrieval.
  • anon_id — the visitor's persistent ID from localStorage, generated on first visit.
  • locale — the host page's language (data-locale, else <html lang>), so the widget UI follows the page. Omitted when neither is present.

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 path component /widget/widget.js is stable across every deploy. Customers paste the snippet once and never need to touch it again — bundle updates ship transparently. The ?v=<hash> query mutates on every build, which busts browser caches on existing pages without changing the URL the customer pasted on their site.

Internally /widget/widget.js is served by WidgetBundleController, which streams the latest hashed bundle named in public/widget/manifest.json with Cache-Control: no-cache, must-revalidate and a strong ETag. The hashed file (widget.<hash>.js) is the on- disk artifact; the stable URL is the public contract.

Pre-fix (2026-05-29) the embed snippet exposed the hashed filename directly (e.g. widget.abc123.js); every new build rotated the URL and customers' pasted <script> tags 404'd. The fix landed in AgentController::widgetUrl() and OnboardingController::buildWidgetSrc().