Web widget
Identify users on the web widget
Sign your users in to the widget with an identity token from your backend.
To tell the Mentiora platform who is chatting, your backend mints an identity token and your page hands it to the widget. Without a token the visitor is anonymous.
Create an identity key
On the Install tab, create an identity key under Identity keys. Store the secret on your server, for example as MENTIORA_IDENTITY_SECRET. Never send it to the browser: anyone with it can sign in as any user.
The keys belong to the project, so every widget in the project accepts the same tokens. Up to two keys can be live at once, and tokens signed with either are accepted. To rotate, create a new key, deploy its secret, then retire the old key.
Mint a token on your server
The token is a JWT signed with HS256:
| Claim | Required | Value |
|---|---|---|
sub | yes | Your stable, non-guessable user id. Never an email. |
aud | yes | "mentiora" |
iat | yes | Issue time. 60 seconds of clock skew is allowed. |
exp | yes | Expiry. You choose the lifetime. |
| any other | no | Copied onto the user's profile in Mentiora. |
Each token replaces the profile attributes the previous one set, so send every claim every time. Omitting a claim removes it.
A Node.js endpoint using jsonwebtoken:
app.post("/me/mentiora-token", (req, res) => {
const userId = req.session?.userId;
if (!userId) return res.sendStatus(401);
const token = jwt.sign(
{ sub: userId, plan: req.user.plan },
process.env.MENTIORA_IDENTITY_SECRET,
{ algorithm: "HS256", audience: "mentiora", expiresIn: "15m" },
);
res.set("Cache-Control", "no-store").json({ token });
});Protect this endpoint with your normal session check and CSRF protection. It issues credentials.
Register the token callback
Paste this block above the install snippet:
<script>
window.mentiora = window.mentiora || function () {
(window.mentiora.q = window.mentiora.q || []).push(arguments);
};
window.mentiora("identify", async () => {
const res = await fetch("/me/mentiora-token", {
method: "POST",
redirect: "error",
cache: "no-store",
credentials: "same-origin",
});
if (res.status === 401) return null; // signed out
if (!res.ok) throw new Error(`status ${res.status}`);
return (await res.json()).token;
});
</script>The first two lines define a queue, so mentiora(...) calls made before the loader downloads run once it arrives.
Return null when nobody is signed in. Throw when you could not find out. A null returned for a server error signs the visitor out without any report.
The widget calls your function when it starts a session, before each session refresh, and once more after a rejected token. It does not start a session at page load, only when the visitor starts to engage, so the first call can come minutes later or never. Mint a fresh token on each call instead of returning one rendered into the page.
Sign-in and sign-out
- On sign-in, call
mentiora("identify", …)again. An anonymous conversation in progress becomes the user's and keeps its history. - On sign-out, call
mentiora("logout"). This ends the session and deletes the stored token.
In a single-page app, wire both into your auth state changes.
Identity errors
To receive token failures, pass an object:
mentiora("identify", {
getToken: fetchMentioraToken, // your token function
onError: ({ reason, message }) => {
console.warn("Mentiora identity", reason, message);
},
});reason | When |
|---|---|
token_rejected | The token failed verification: bad signature, wrong aud, or a retired key. |
token_expired | The token's exp had passed. |
callback_failed | Your getToken threw or returned something that is not a token. |
network | The request to the Mentiora platform got no answer. |
identity_required | The widget accepts only signed-in users. |
onError is informational. The widget falls back to an anonymous session either way. Without onError, it logs one console warning per reason.
Signed-in users only
Only authorized users under Access on the Install tab stops anonymous visitors from starting a chat, on your website and in your apps. It needs an identity key.