Install the AI agent
One script line, pasted once. Penzago ships every improvement to your agent automatically — you never touch the line again.
The line
Your Penzago contact gives you a widget key (agent_…). Paste this anywhere in your page — it floats a chat button in a corner:
<script async src="https://www.penzago.com/agent.js" data-reflow-agent="agent_YOURKEY"></script>Or embed the agent as a card in your layout. With data-mode="inline" the card renders exactly where the line sits, so paste it at the spot in the page the card should occupy:
<script async src="https://www.penzago.com/agent.js" data-reflow-agent="agent_YOURKEY" data-mode="inline"></script>| Attribute | Required | What it does |
|---|---|---|
data-reflow-agent | yes | Your widget key. Without it, nothing renders. |
data-mode | no | inline for the in-page card. Anything else (or nothing) is the floating launcher. |
data-position | no | left or right — pins the launcher to that corner, overriding the side configured in Penzago. |
How updates reach you
The pasted line is a tiny loader with a stable URL. On each page load it reads a short-lived manifest and loads the current agent runtime — a content-hashed, immutably-cached file. When we ship an improvement we publish a new hash; your pages pick it up within minutes, and the line you pasted never changes. If anything in that chain ever fails, the widget simply does not appear — your page is never affected.
Before it will render
Your widget answers only on domains you have approved — that is a feature, not a hurdle: your key is visible in your page source, and the allowlist is what makes it useless to anyone who copies it onto another site. The match is exact, so https://www.example.com and https://example.com are separate entries — give your Penzago contact every hostname you embed on, staging included. On a domain that is not on the list, the widget renders nothing and your page stays untouched.
React, Vue and other SPAs
If your framework re-renders the DOM, load the runtime with a bare line (no key) and place the element yourself, wherever your component tree wants it:
<script async src="https://www.penzago.com/agent.js"></script>
<reflow-agent agent-key="agent_YOURKEY" mode="inline"></reflow-agent><reflow-agent> is a standard custom element: render it from JSX/templates like any tag. Attributes are read once when the element enters the page — to change one, remove the element and re-add it. A remount restores the visitor's conversation automatically.
Styling the inline card
The inline card inherits your page's font by default and exposes a small set of CSS custom properties — set them on the element and leave the rest alone:
reflow-agent {
--rfa-primary: #0f5132;
--rfa-bg: #fbfaf7;
--rfa-ink: #1b1b1b;
--rfa-radius: 4px;
}Your values outrank the office theme, which outranks the built-in defaults. The floating launcher ignores host styling entirely — it is the business's presence and looks the same on every page. Class names inside the widget's shadow root are private and change without notice; the custom-property surface is the only stable styling API.
Content-Security-Policy
If your site ships a CSP, add these to your existing policy:
| Directive | Add | Why |
|---|---|---|
script-src | https://www.penzago.com | the pasted agent.js and the versioned runtime it injects |
connect-src | https://www.penzago.com | manifest, config, chat stream and analytics beacon — every request the widget makes |
style-src | 'unsafe-inline' | the widget's own stylesheet, injected inside its shadow root |
A complete policy for a fully locked-down page — our test suite runs the widget under exactly this policy on every build, so it cannot drift from reality:
default-src 'none'; script-src 'self' https://www.penzago.com; connect-src https://www.penzago.com; style-src 'unsafe-inline'; img-src 'self'; base-uri 'none'For the reviewer's checklist: frame-ancestors is not involved — the widget is a custom element in your own DOM; it neither frames your page nor itself. No font-src entry is needed — themes use font stacks, never webfont URLs. img-src only comes up when your agent is configured with a logo, which is served from Penzago's storage domain; if you use one, we'll give you the exact host to allow.
If your policy uses 'strict-dynamic', host allowlists are ignored by design — put your nonce on the pasted <script> tag instead (nonce="…" next to async). The loader injects the agent runtime as a non-parser-inserted script, so under 'strict-dynamic' it inherits the pasted tag's trust automatically; connect-src still needs the entry above.