Skip to main content
Every conversation belongs to one person. Nobody else can list it, read it, or write to it, even knowing its id. Who that person is depends on your product:

No login

No accounts, or a public page. Nothing to do.

Same domain

Extra served from your site. Config only.

Other domain

Extra on its own hostname. One endpoint.

No login

Each browser gets its own signed pass and its own conversations. Two people on the same page cannot see each other’s chats.

Same domain

Serve Extra from a path on the site your users already sign into — say acme.com/agents — and it reads the session they already have.
No code. The browser sends your cookie because it’s the same origin, and Extra verifies it with your own secret.
Works with any app that issues a session JWT — Open WebUI, Django, Rails, most Node stacks. If your user id isn’t in the sub claim, set EXTRA_AUTH_CLAIM_USER_ID.

Other domain

Browsers won’t send your session cookie to a different hostname, so your backend vouches for the user instead.
Not logged in? Return 401 — the widget falls back to an anonymous pass.
token-url is resolved like any other browser request: relative to the page. That is correct in production, where your app and its API share an origin. If your dev setup serves the frontend separately from your API, point it at the same base your own frontend uses.

When identity fails

Anything other than a clean token is reported — a wrong URL, an endpoint answering with the wrong shape, an unreachable host. The widget logs a warning and raises an event, so a broken integration cannot quietly look like a working anonymous chat:
By default it then continues as an anonymous visitor. If a chat with no proven user is unacceptable in your product, opt out of that fallback entirely:
Sign req.user.id from the session, never a value from the request. An endpoint that accepts ?user= lets anyone get a token as anyone.

Signing in keeps the conversation

Someone who chats before logging in doesn’t lose it. On their first authenticated request the widget hands their pass over and those conversations move onto their account. Automatic, and it only happens once.

Signing in and out

A normal page navigation needs nothing — the widget works out who the caller is on every load. A single-page app that signs a user in or out without reloading should say so, or the widget keeps the identity it already resolved:
Use refreshIdentity() on sign-in, not logout(). logout() discards the visitor pass, and that pass is what carries a visitor’s earlier conversations onto their account — calling it here throws them away instead of merging them.

Settings

Rotating EXTRA_AUTH_SECRET is safe: widgets fetch a fresh token on their next request.