Quincer AI Docs Home Support Start free

Connect

Visitor sign-in

Verify who the visitor is on your platform before the AI calls any secure custom tool. Required for “What’s my balance?”, “Show my order”, “Cancel my subscription” — anything tied to a specific user account. Public tools (“track this package by tracking number”) work without sign-in.

i

Visitor sign-in is the foundation; the actual gating of which tools require it lives on each Custom tool (separate feature). You configure the verification here; you mark individual tools as “requires sign-in” on the Custom tools page.

When you need this

Two example flows side-by-side:

ScenarioSign-in required?
Visitor asks “where is order #ABC123?” (anyone can pass an order number) No
Visitor asks “what’s my account balance?” (must be the right person) Yes
Visitor asks “cancel my latest subscription” Yes
Visitor asks “what are your business hours?” No (knowledge-base answer)

How it works (architecture)

  1. Visitor signs in on your platform via your existing flow (email/password, SSO, Google, whatever).
  2. Your platform produces a signed token proving who the visitor is — either a standard JWT signed with public-key crypto (recommended) or a JWT signed with a shared HMAC secret.
  3. Your page calls ChatWidget.identify({ token }) after sign-in. The widget attaches the token to every chat request.
  4. Our chat backend verifies the token against the keys you registered on the Visitor sign-in config page.
  5. Verified identity is attached to the request context. Any custom tool marked “requires sign-in” can now run and gets the verified user id automatically.

We never store your visitor passwords. We only verify a token your auth system already issued.

Mode 1 — JWT + JWKS (recommended)

Your platform issues JWTs and exposes a JWKS endpoint with the public keys. We pull the keys, cache them for 15 minutes, and verify token signatures locally on every chat request.

Auth0 example

  1. In our dashboard: Integrations → Visitor sign-in → JWT + JWKS.
  2. JWKS URL: https://YOUR_DOMAIN.auth0.com/.well-known/jwks.json
  3. Issuer (optional): https://YOUR_DOMAIN.auth0.com/
  4. Audience (optional): the API identifier you set up in Auth0.
  5. Save.

On your page, after Auth0 login completes:

const auth0 = await createAuth0Client({ ... });
const token = await auth0.getTokenSilently();
ChatWidget.identify({ token });

Amazon Cognito example

Use the User Pool ID’s discovery doc — Cognito serves a standards-compliant JWKS at:

https://cognito-idp.{REGION}.amazonaws.com/{USER_POOL_ID}/.well-known/jwks.json

Issuer: https://cognito-idp.{REGION}.amazonaws.com/{USER_POOL_ID}.

Clerk / Firebase Auth / generic OIDC

Same pattern. Any provider that serves a JWKS works. Common endpoints:

Key rotation

Keys cache for 15 min. After you rotate keys, click the “Refresh JWKS + verify” button on the Test card to force an immediate refresh. Production traffic refreshes automatically on the next chat request after the cache expires.

Mode 2 — HMAC shared secret

Simpler if you don’t already have public-key infrastructure. Your backend signs a JWT with HS256 + a shared secret we store encrypted at rest. Less recommended than JWT/JWKS only because a leaked secret is a one-step path to forged identities — rotate it if you suspect compromise.

Setup

  1. In our dashboard: Integrations → Visitor sign-in → HMAC shared secret.
  2. Click Generate to create a random 32-byte secret. Copy it to your backend’s secrets manager.
  3. Save.

Node example (sign in your backend)

Use the same jose library we use for verification:

import { SignJWT } from "jose";

const secret = new TextEncoder().encode(process.env.QUINCER_VISITOR_SECRET);

// Call this after your user signs in.
async function tokenForUser(user) {
  return new SignJWT({
    email: user.email,
    name: user.name,
    plan: user.plan,
  })
    .setProtectedHeader({ alg: "HS256" })
    .setSubject(user.id)          // → ends up as the canonical user id
    .setIssuedAt()
    .setExpirationTime("1h")       // short-lived; rotate on refresh
    .sign(secret);
}

Pass the token to the widget

On the page where your user lands after signing in:

<script>
  // Server-rendered: include the token in your HTML response.
  ChatWidget.identify({ token: "{{ JWT_FROM_BACKEND }}" });
</script>

Or fetch the token from your own /api/me/quincer-token endpoint after sign-in.

If you want the widget itself to prompt the visitor to sign in when a secure tool is needed, register a Sign-in URL in the config. The widget opens this URL in a new tab when an unauthenticated visitor asks for a secure tool; your sign-in page posts the token back when login succeeds.

Your sign-in page

// after login completes:
const token = await getQuincerTokenForCurrentUser();
window.opener.postMessage(
  { type: "quincer-identify", token },
  // Match the origin where the widget is embedded.
  "https://your-customer-site.com"
);
window.close();

The widget verifies the message origin matches the configured Sign-in URL before accepting the token, so a random tab can’t inject one.

Claim mapping

The model only sees what you tell it to see. The verifier extracts standard JWT claims by default (everything except iss/sub/aud/exp/iat/nbf/jti) and passes them to tools that need identity. To rename or restrict that, fill in the Claim mapping JSON. Example:

{
  "userId": "sub",
  "email": "email",
  "tier": "https://acme.example/tier",
  "accountId": "https://acme.example/account_id"
}

Now a custom tool that lists requiredClaims: ["userId", "tier"] works as expected, even though the JWT uses Auth0’s namespaced custom-claim convention.

Testing your setup

The config page has a built-in Test a token card:

  1. Sign in on your platform and grab a real token (browser dev tools, your backend, your auth provider’s dashboard).
  2. Paste it into the Test card and click Verify.
  3. You’ll see either a green “Verified” result with the resolved identity (this is what the AI tools see), or a clear failure reason and message.

Use this to iterate on the JWKS URL, the issuer/audience values, and the claim mapping without bouncing through your full sign-in flow.

Common failure reasons

ReasonWhat it meansFix
missing-token No Authorization: Bearer header on the request. Call ChatWidget.identify({ token }) before the visitor sends a message.
config-incomplete You picked a mode but didn’t finish setup. JWT mode needs a JWKS URL; HMAC needs a secret.
expired The token’s exp is past. Refresh the token in your sign-in flow and re-call identify.
bad-signature Token signed with a different key than we have. Rotated keys? Click Refresh JWKS + verify. HMAC mismatch? Re-copy the secret to your backend.
bad-claim iss / aud enforced and the token doesn’t match; or no sub claim. Check the issuer/audience values, or stop enforcing them. Every token must have sub — we use it as the stable user id.
jwks-fetch-failed We couldn’t reach the JWKS URL. Confirm the URL is public (no auth required) and serves JSON.

Security notes

!

Your visitor session expiration is yours to control. We honor whatever exp your JWT carries. Don’t issue 1-year tokens — a leaked one is valid for that whole window.