Drop-in Widget — pouchy.ai/embed
The zero-code way to put a Pouchy companion in any page: one <iframe> renders
a complete chat UI (bubbles, composer, streaming replies), authenticated by the
same session token your backend already mints (POST /v1/sessions) or a user's
PAT. When you outgrow the canned UI, everything here is available headless via
@pouchy_ai/companion-sdk — the widget is built on it.
Embed
<iframe
src="https://pouchy.ai/embed?token=SESSION_TOKEN&theme=dark&accent=%23ff6b81"
style="width: 380px; height: 560px; border: 0; border-radius: 16px"
allow="clipboard-write"
></iframe>
Query params
| Param | Values | Effect |
|---|---|---|
token |
pchy_… |
Session token or PAT. Optional — can be delivered via pouchy:init instead (below). |
theme |
dark |
Dark palette. Default light. |
accent |
%23rrggbb (URL-encoded hex) |
Recolors the send button + user bubbles. Text ink is auto-picked by luminance so any accent stays readable. |
postMessage protocol
For parents that prefer not to put the token in a URL, and to drive the widget
live. Every message the widget posts carries source: 'pouchy-widget'.
Widget → parent:
| Type | Payload | When |
|---|---|---|
pouchy:loaded |
— | The page booted (safe moment to send pouchy:init). |
pouchy:ready |
{ session } |
Connected; the companion is live. |
pouchy:message |
{ text } |
An assistant reply landed. |
pouchy:sent |
{ text } |
A user message left the composer (typed or via pouchy:send). |
pouchy:tool_call |
{ id, name, args } |
The companion called an app tool (declare tools via the SDK for full round-trips). |
pouchy:error |
{ code?, message } |
Connect/turn error. |
Parent → widget:
| Type | Payload | Effect |
|---|---|---|
pouchy:init |
{ token, surface? } |
Connect with this token (alternative to ?token=). |
pouchy:send |
{ text } |
Send a user message programmatically. |
pouchy:context |
{ event } |
Push a world-state event (same shape as SDK sendWorldState). |
const frame = document.querySelector('iframe');
window.addEventListener('message', (ev) => {
if (ev.data?.source !== 'pouchy-widget') return;
if (ev.data.type === 'pouchy:loaded')
frame.contentWindow.postMessage({ type: 'pouchy:init', token: SESSION_TOKEN }, 'https://pouchy.ai');
if (ev.data.type === 'pouchy:message') console.log('companion said:', ev.data.text);
});
Security model
The embedding page is the trust anchor. The widget pins the embedder's origin
(learned from the referrer at load, or adopted from the first parent message
when the referrer is stripped) and afterwards only accepts control messages
from that origin and only posts session/tool payloads back to it — never to
'*', never to sibling frames or popups (ev.source !== window.parent is
rejected outright). Treat session tokens like the credentials they are: mint
them short-lived per user from your backend.
Known limits (v1)
- Widget chrome is English (the conversation itself follows the agent/user
language). A
langparam is a planned follow-up. - Text modality only — for voice, use the SDK's
connectCallin your own UI.