Signed identity
A plain identify call comes from the browser, so anyone can change it.
Signed identity fixes that. Your server creates a short-lived JWT that proves
who the user is, and the widget sends it to UserJot.
With a signed identity, everything in the token is verified: the user ID, email, name, traits, and companies. Nothing can be spoofed from the browser.
When you need this
- You want to trust that feedback and messages really come from the user they claim to come from.
- You enabled Require signed tokens in your workspace settings. Unsigned identify calls are then rejected.
- Workspace admins and owners always need a signed identity. Unsigned identification is never accepted for privileged accounts.
1. Create the token on your server
Sign a JWT with the HS256 algorithm using your workspace secret. You can find both values in Settings → Login, in the Secrets section, listed as Project ID and Project secret.
Never sign tokens in the browser. The secret must stay on your server.
The token must include these claims:
| Claim | Value |
|---|---|
sub | The user's ID in your system. Required. |
iss | Your UserJot project ID. Required. |
aud | The exact string userjot. Required. |
iat | Issued at, in Unix seconds. Required. |
exp | Expiry, in Unix seconds. Required. Maximum 1 hour after iat. |
You can also include these optional claims: email, firstName, lastName,
avatar, traits, and companies. Anything you put in the token is treated
as verified.
import jwt from 'jsonwebtoken';
export function createUserJotToken(user, secret, projectId) {
return jwt.sign(
{
sub: user.id,
email: user.email,
firstName: user.firstName,
lastName: user.lastName,
iss: projectId,
aud: 'userjot'
},
secret,
{ algorithm: 'HS256', expiresIn: '1h' }
);
}Create a fresh token on each page load, for example when you render the page or through a small endpoint your frontend calls. Tokens are capped at 1 hour, so do not cache them for longer.
2. Pass the token to identify
Send the token in the token field:
uj.identify({
token: 'TOKEN_FROM_YOUR_SERVER'
});You can still pass user and companies alongside the token. They are used
as extra, unverified detail. If a field appears in both, the token wins:
uj.identify({
token: 'TOKEN_FROM_YOUR_SERVER',
user: { id: 'user-123' },
companies: [{ id: 'acme', name: 'ACME Inc.' }]
});You can confirm the identity was verified from the result:
const result = await uj.identify({ token });
result.identityTrust; // 'signed'Invalid tokens fail, they do not fall back
If the token is expired, malformed, or signed with the wrong secret, the
identify call rejects with IDENTIFY_FAILED. It does not silently continue
as an unsigned identity. This is deliberate: a broken signing setup should be
loud, not invisible.
Signed identity and conversations
Signed identity is what attaches widget conversations to a verified account. Two behaviors are worth knowing:
- If a visitor starts a conversation anonymously and then identifies with a signed token, their guest conversation is handed off to the member account, so history follows them.
- A conversation started under a signed identity stays protected. Opening it later without a signed session shows "Verify to view this conversation" until they identify again with a signed token.
Requiring signed tokens does not close the door on anonymous visitors: they
can still start guest conversations scoped to their browser. What strict mode
changes is that a conversation can only be attached to a member account
through a signed token — unsigned identify calls are rejected outright.
Identity carries over to your public pages
Once a user is identified, the SDK automatically attaches the identity to links from your product that point to your public UserJot pages, like your board, roadmap, or changelog. For the user to land there already signed in, your workspace also needs Automatic login enabled in settings — that is the mechanism the public pages use to accept the identity. Without it, the links still work but the user arrives signed out.
Troubleshooting
The identify call rejects with IDENTIFY_FAILED
Check these:
- The token is signed with HS256 and your current workspace secret.
issmatches your project ID andaudis exactlyuserjot.- Both
iatandexpare present, in Unix seconds. - The lifetime is 1 hour or less. Longer tokens are rejected.
- Your server clock is accurate. Tokens issued in the future are rejected, with 60 seconds of allowed clock skew.
Unsigned identify stopped working
That usually means Require signed tokens was enabled for your workspace. You can find it in Settings → Login, where it appears once Automatic login is on. Once it is enabled, every identify call needs a token.