Visitors & verified users
Everyone who talks to your agent on your site is their own user of that agent — never you. Their conversations and the accounts they connect are theirs, and you see them all in the agent's Users tab.
By default: one visitor per browser
Paste the tag and you're done. Each browser that opens the chat becomes its own visitor, remembered by a random secret kept in the chat's own storage (your page can't read it). When they come back on the same browser, they find their conversations and their connected accounts again.
On another browser or device, or after clearing their data, they start as a new visitor. To follow the same person everywhere, use verified users below.
Visitors' own accounts
In the Embed tab, the Visitors' own accounts checkbox decides whose accounts the agent uses when it reaches Gmail, Notion and the other connectors:
| Setting | What happens |
|---|---|
| On | Each visitor connects their own accounts, from the plug icon in the chat header or the + → Connectors menu. The agent acts on their accounts, never yours. |
| Off | The agent uses the accounts you connected. Visitors connect nothing. |
Which connectors the agent may use is always your choice, set on the agent.
The Users tab
Open your agent, then the Users tab:
- Name, E-mail, Id — what your site told us about the visitor.
- Chats — how many conversations they had with the agent.
- Last seen, Suspend / Restore — a suspended visitor can no longer open the chat.
- Click a row to read their conversations (read only).
- Search and CSV export.
A value your site sent with identify without proof
is shown with a grey declared tag: it's there for convenience and never used
to tell one visitor from another. A shield marks a verified user.
Verified users (advanced)
For sites that already have their own user accounts: your site proves who the user is with a token signed by your private key. We only hold the matching public key, so we can check a token but never make one — nobody, not even us, can pretend to be one of your users.
A verified user is the same user on every device: same conversations, same connected accounts.
1. Add your public key
Users tab → Add key, paste the public key. Accepted formats: PEM
(-----BEGIN PUBLIC KEY-----), a certificate (-----BEGIN CERTIFICATE-----),
OpenSSH (ssh-ed25519 …, ecdsa-sha2-…, ssh-rsa …), a JWK, or a key set
({"keys": [...]}) holding one signing key.
| Key type | Token alg |
|---|---|
| EC P-256 | ES256 |
| EC P-384 | ES384 |
| EC P-521 | ES512 |
| RSA, 2048 bits or more | RS256, RS384, RS512, PS256, PS384, PS512 |
| Ed25519 | EdDSA |
A private key pasted by mistake is refused. You can add several keys (to rotate without downtime); removing one stops its tokens at once.
To create a pair:
openssl ecparam -name prime256v1 -genkey -noout -out private.pem # stays on your server
openssl ec -in private.pem -pubout -out public.pem # paste this one
2. Sign a token for the signed-in user, on your server
A JWT with these claims:
| Claim | ||
|---|---|---|
sub | required | Your user's unique, stable id. The same sub is the same user everywhere. Up to 255 characters are kept. |
exp | required | Expiry (Unix seconds). Keep it short — an hour or less. 30 s of clock tolerance. |
name | optional | Shown in the Users tab. |
email | optional | Shown in the Users tab and the CSV export. |
nbf, iat | optional | Checked when present. |
Other claims (iss, aud, …) are ignored. A token is at most 8 KB and holds
nothing secret — the visitor can read it, just not forge it.
// Node — npm i jose
import { SignJWT, importPKCS8 } from "jose";
const key = await importPKCS8(process.env.DIGITORN_PRIVATE_KEY, "ES256");
const token = await new SignJWT({ name: user.name, email: user.email })
.setProtectedHeader({ alg: "ES256" })
.setSubject(String(user.id))
.setExpirationTime("1h")
.sign(key);
// PHP — composer require firebase/php-jwt
use Firebase\JWT\JWT;
$token = JWT::encode([
'sub' => (string) $user->id,
'name' => $user->name,
'email' => $user->email,
'exp' => time() + 3600,
], getenv('DIGITORN_PRIVATE_KEY'), 'ES256');
# Python — pip install "pyjwt[crypto]"
import os, time, jwt
token = jwt.encode(
{"sub": str(user.id), "name": user.name, "email": user.email, "exp": int(time.time()) + 3600},
os.environ["DIGITORN_PRIVATE_KEY"],
algorithm="ES256",
)
Never sign in the browser: a private key in your page is readable by every visitor.
3. Pass it to the widget
<script src="https://digitorn.ai/embed.js" data-key="dk_…"></script>
<script>
digitornChat.identify({ token: "<?= $token ?>" });
</script>
When the user signs out of your site: digitornChat.identify(null).
Call identify again whenever you have a fresh token: the chat doesn't reload as
long as it's the same user, and switches to the right user when it changes.
Already using a sign-in service?
If its tokens are signed with a public key that stays the same, paste that key and pass the token you already have — no server code to write:
digitornChat.identify({ token: await getToken() });
- Clerk — the instance's JWT public key (dashboard → API keys).
- Auth0 — your tenant's signing certificate, or the content of
https://<tenant>.auth0.com/.well-known/jwks.json. - Supabase — projects using the asymmetric (ES256) signing keys: the content
of
https://<project>.supabase.co/auth/v1/jwks. Projects still on the legacy shared secret (HS256) can't be verified with a public key.
When the service rotates its key, add the new one before removing the old.
Firebase isn't supported this way: Google rotates its signing keys every few hours and shares them across every Firebase project. Sign your own token on a server instead.
What is refused
The visitor simply stays an anonymous visitor — never someone else — when the
token is: missing, signed with another key, unsigned (alg: none), expired,
without exp, altered after signing, or presented on another embed key. A plain
identify({ id }) never makes anyone verified.