Skip to content
Early preview — you found Guuey before launch · official launch soon

Embed & share

read as .md

Any deployed agent can live on your own website as a chat widget. The widget runs in an iframe on guuey’s origin, so it never touches your page’s cookies, storage, or scripts — the only thing you add to your site is two <script> tags.

You need two things: your agent’s app id (shown in Studio and the console, and printed by guuey apps list) and your site’s domain on the app’s allowed list.

The widget is embeddable only on domains you’ve explicitly allowed. Until your site’s origin is on the app’s allowed domains, the browser refuses the frame and the widget stays blank — this is deliberate, and it applies to every app from the moment it’s created.

Add the origin your pages are served from (scheme + host, e.g. https://www.example.com):

  • Studio — open your agent’s Share drawer and add it under Allowed origins, right below the embed snippet.
  • Console — open your app, go to the Embed tab, and add it under Allowed origins.
  • CLIguuey apps update <appId> --domains https://www.example.com — the flag sets the app’s complete comma-separated allowlist (it replaces what was stored; an empty value clears it), so include every origin the app should allow.

Then verify it — guuey apps check <appId> --origin https://www.example.com (the app id is optional inside a project with a configured app-id) sends the same CORS preflight your visitor’s browser will send to the live agent endpoint and prints the verdict, so you’re checking the live allowlist rather than what you remember configuring.

One list, two readers. The same allowlist is read by two checks with different rules, and the widget’s is the stricter one. The widget’s frame check (a frame-ancestors policy) matches a bare domain such as example.com to that exact host only — www.example.com is not covered — and grants no implicit localhost. Your agent’s endpoint, which the widget calls once it has mounted, is looser with the same entries: a bare domain covers https:// on the apex and every subdomain, and http://localhost is always accepted. guuey apps check exercises the endpoint’s check, so it can pass for a www origin on a bare-domain entry while the frame still refuses. For embeds, list explicit origins (https://www.example.com, and http://localhost:3000 for local development) — an entry that satisfies the frame check satisfies the endpoint too, as long as you write origins the way a browser serializes them: no explicit :443/:80, which browsers omit and the endpoint matches byte-for-byte.

This allowlist controls where the widget may be embedded. To serve the agent’s own chat page from a hostname you own instead, see Your own domain.

Copy the snippet from your agent’s Share drawer in Studio (under Embed on your website, next to the allowed-origins editor — both steps live in one place there) or from the console’s Embed tab — both render it with your real app id. It looks like this:

<!-- guuey widget. Add this site to the app's allowed domains or the embed is refused. -->
<script>
window.guuey = window.guuey || function(){(guuey.q=guuey.q||[]).push(arguments)};
guuey("init", {
app: "app_abc123",
});
</script>
<script src="https://widget.guuey.com/v1.js" async></script>

Paste it anywhere in your HTML — before </body> is typical. No SDK, no build step, no framework required.

Keep the snippet exactly as generated. The first line registers a small queue so calls made before the loader finishes downloading are replayed when it arrives — that’s what makes the async load safe. Some code formatters try to re-wrap the one-line shim; keep it byte-identical to the generated snippet so it always diffs clean against what Studio and the console emit.

The loader is a permanent contract: paste it once and it keeps working. New capabilities ship behind the frame, on guuey’s release cadence, without you touching your page.

One serving caveat: if your app’s 7-day free trial ends without a plan, the embedded widget shows visitors a clear notice that the agent is paused instead of a chat — see Plans & billing.

Options go in the guuey("init", …) call:

Option Values Default What it does
app string required Your app id.
theme "light" / "dark" "light" "dark" paints the chat panel dark, from the first frame.
launcher string "Chat" Accessible label for the launcher button and the chat frame.
color CSS color #111 Launcher bubble background.
iconColor CSS color #fff Launcher icon color.
position "left" / "right" "right" Which side of the viewport the widget docks to.

theme only picks the light or dark variant. The panel’s full look — colors, typography, shape — follows the chat theme set on your app’s Design page in the platform console; the widget picks up changes without you touching the snippet. See Chat theming.

You can also open and close the panel from your own UI:

guuey("open");
guuey("close");

Unknown or invalid values are ignored with a console warning — the widget never throws into your page.

When your agent presents an interactive surface — a generative UI card or canvas — the open panel expands to a near-fullscreen overlay on your page, and returns to the docked panel when the surface is done. The widget never opens itself over your page: only a panel the visitor has already opened can expand. Cards can also carry follow-up action buttons — tapping one stages text into the composer for the visitor to edit and send; it never sends on its own.

Anonymous (the default). Visitors chat without signing in. The widget keeps a per-browser identity in its own storage so a returning visitor continues their conversation; continuity is best-effort (strict privacy modes may reset it). If your agent is set to require sign-in to chat, anonymous embeds can’t chat — use identified embeds instead.

Identified (your own sign-in). If your site has its own logged-in users, the widget can know who they are — giving each visitor durable history tied to their account with you. This mode is configured per app in the console’s Embed tab, and takes three steps:

  1. Enrol your app once and store the secret it prints (shown once — treat it like a database password):

    Terminal window
    guuey widget keys create <appId> --audience <your-audience>
  2. Add a token endpoint to your backend with @guuey/widget-auth — a zero-dependency package that mints a short-lived token for the currently signed-in user:

    import { signUserToken } from "@guuey/widget-auth";
    const { token } = await signUserToken(
    { userId: session.userId, name: session.name, email: session.email },
    { appId: process.env.GUUEY_APP_ID!, appSecret: process.env.GUUEY_APP_SECRET! }
    );
    // Return the raw token string from your endpoint.
  3. Point the widget at it. The console’s Embed tab generates the identified snippet, which adds an identity block:

    identity: {
    getToken: (reason) =>
    fetch("https://www.example.com/api/guuey-token" + "?reason=" + reason).then((r) => {
    if (!r.ok) throw new Error("token endpoint failed: " + r.status);
    return r.text();
    }),
    },

    getToken is called with a reason: "initial" on first need, "expired" when a token has stopped working. If your endpoint caches tokens, always mint a fresh one on "expired".

An app configured for identified embeds fails loudly, never silently: if the identity setup is incomplete, the widget shows a clear notice instead of mounting a chat that would drop your visitors into anonymous threads.

The same token endpoint also signs users into a surface you build yourself on the chat kit or the client SDK — same token, same identity, so a visitor is one person across the widget and your own page.

Every agent can be shared as a hosted chat page — no website required. Open your agent in Studio and choose Share.

The link points at guuey’s agent client:

https://app.guuey.com/agent/<app-id>

Anyone who opens it gets a full chat with your agent on the web (native mobile apps are on the way).

  • Unlisted link — the agent stays off the public Discover directory (the same surface Portal presents as its Explore tab) and is reachable only by its direct link. Available for any agent.
  • List in Discover — the agent is also listed publicly in the Discover directory, where anyone can find it. Studio only offers public listing for read-only agents — ones with no connected accounts that could modify data (a Studio-side check, not a platform rule); if your agent gains a connected account later, you’ll be prompted to switch it back to unlisted.

Stop sharing removes the agent from Discover; the direct link and the agent’s page keep working for any live agent, and resuming later restores the same listing. To actually restrict who can chat, turn on Require sign-in to chat (below) — that is the access gate, not the listing.

Separate from how the agent is listed, you choose whether visitors must sign in before chatting. This is the real access gate — enforced by the agent itself, and it applies to the share link even when the agent is unlisted. Changes take effect within about 15 seconds.

Optionally add a description and category in the Share panel. These appear on Discover cards and the agent’s page once the listing is public and published; they change nothing for unlisted agents.

The agent’s icon — along with the social-share image and accent color — is app branding, not listing metadata: set it in the console under Agent Settings → General → Branding or with guuey apps update --brand-icon-url, and it applies whether or not the agent is listed. The welcome line belongs to the standalone page instead — Agent Settings → General → Standalone page, or guuey apps update --welcome-copy. See Design & distribution.

The widget stays blank or never mounts. Almost always the page’s origin is missing from the app’s allowed domains — the browser refuses the frame by design. Add the exact origin your pages are served from (scheme + host, no path) and verify with guuey apps check <appId> --origin https://www.example.com; a www / apex mismatch is the classic miss.

Visitors see a notice instead of a chat (identified embeds). An app configured for identified embeds refuses to mount an anonymous chat when the identity setup is incomplete. Check that your token endpoint is reachable from the page and returns the raw token string, and that it mints a fresh token when getToken is called with "expired".

Visitors see that the agent is paused. The app’s free trial ended without a plan (or the app was paused). Choosing a plan resumes it automatically within a few minutes — see Plans & billing.

A share link stopped working. Stop sharing only delists the agent from Discover — the direct link keeps working for any live agent, so a dead link means the app was deleted, not unlisted (a paused agent’s page still loads and says it is paused — see the bullet above). To restrict who can chat, use Require sign-in to chat.