Your identity endpoint
read as.mdIf your app uses bring-your-own auth and is served on a custom domain, visitors sign in through an identity endpoint you host: a URL on your own site that recognizes the visitor’s existing session cookie and returns a short-lived Guuey token for them. The agent page never sees your credentials or your session — it only receives the token your endpoint chooses to mint.
This is the standalone-page counterpart of the identified embed’s token
endpoint: the minting is identical, only the
transport differs. It is one of two bring-your-own ways to sign visitors
into the page — the other, a redirect to your own identity
provider, works on every host (Guuey’s included) but
costs a full-page hop; this one is silent where your session cookie is
reachable. An embed lives inside your page and can call your
backend directly; a standalone page on chat.example.com is its own
origin, so the browser fetches your endpoint cross-origin with
credentials — which is why the CORS contract below is exact.
How it works
Section titled “How it works”-
A visitor opens your agent on your custom domain, already signed in to your site in the same browser.
-
The page requests your identity endpoint — a plain
GETwithcredentials: "include"andAccept: application/json. The visitor’s session cookie is the only credential. -
Your endpoint authenticates the cookie, mints a token for that user with
@guuey/widget-auth, and responds200with:{ "token": "<the signed token>" } -
The page uses that token for the chat. Conversation history, memory, and profile all key off the identity your endpoint asserted — the same identity every other Guuey surface derives from that token; how that works is on The identity model.
There is no guest fallback on this path: if your endpoint can’t be reached or answers anything but the contract above, the page shows a clear “sign-in unavailable” notice rather than silently dropping your visitor into an anonymous session. When a token expires, the page silently re-queries your endpoint — the visitor’s session cookie is still in the browser, so renewal is just another fetch, no prompt. Only if that fetch fails does the visitor see the “sign-in service didn’t respond” notice with a Try again button.
Configure the URL
Section titled “Configure the URL”The endpoint is part of the app’s standalone page settings (it only ever runs on the agent’s own page). Set it in the console under Settings → General → Standalone page, or:
guuey apps update <appId> --identity-endpoint-url https://www.example.com/api/guuey-identityThe URL must be https://. It is stored on the app whatever the auth
mode, but only used when the app’s authentication is bring-your-own.
The endpoint
Section titled “The endpoint”The same minting code as an identified embed’s token endpoint — enrol the
app once with guuey widget keys create, then:
import { signUserToken } from "@guuey/widget-auth";
// Pseudocode for any Node backend — authenticate YOUR session first.export async function handleGuueyIdentity(req, res) { const session = await readYourSession(req); // your cookie, your rules if (!session) return res.status(401).end();
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!, // The API origin, without `/v1` — or set GUUEY_API_URL. apiBaseUrl: "https://api.us-east-1.guuey.com", } ); res.setHeader("Content-Type", "application/json"); res.json({ token });}The CORS contract
Section titled “The CORS contract”The request arrives cross-origin with credentials, so the browser holds your response to the strictest CORS rules. Your endpoint must send all three headers, exactly:
| Header | Value | Why |
|---|---|---|
Access-Control-Allow-Credentials |
true |
Without it the browser discards the response of a credentialed request. |
Access-Control-Allow-Origin |
The exact requesting origin, e.g. https://chat.example.com — only after you’ve validated it |
* is rejected outright by browsers when credentials are involved, and reflecting any Origin unvalidated turns your session cookie into a token mint for every site on the internet. Validate against the fixed list of hostnames you serve your agent on, and refuse the rest. |
Vary: Origin |
— | You’re returning a per-origin header; without Vary a shared cache can serve one origin’s Allow-Origin to another and break sign-in intermittently. |
const ALLOWED_ORIGINS = new Set([ "https://chat.example.com", // your custom domain(s), nothing else]);
const origin = req.headers.origin;if (origin && ALLOWED_ORIGINS.has(origin)) { res.setHeader("Access-Control-Allow-Origin", origin); res.setHeader("Access-Control-Allow-Credentials", "true");}res.setHeader("Vary", "Origin");The request is a simple GET (no custom headers), so no preflight
OPTIONS handling is required. If you route it through middleware that
adds one anyway, answer the preflight with the same three headers.
A CORS refusal is deliberately invisible to the page — the browser reports it only as a network failure — so if sign-in shows as unavailable, check your endpoint’s response headers in the browser’s network inspector first.
Cookies across subdomains
Section titled “Cookies across subdomains”- If your agent’s custom domain shares a site with your session — say the
page is
chat.example.comand the endpointwww.example.com— the request is same-site, and default (SameSite=Lax) session cookies are sent. Most setups need no cookie changes. - If they are different sites entirely, the cookie must be issued with
SameSite=None; Secureto accompany the request — with all the caveats that setting carries. Keeping the agent’s domain under the same site as your auth is the simpler path.