JWT Authentication
Identify users in your help widget without requiring a separate login. When users are logged into your app, you can pass their identity to Ferndesk using a JWT token signed by your backend.
You'll need your JWT secret from Help Center > Access Control and the Ferndesk SDK installed.
JWT identify is available on all plans and works in both Open and Locked access modes. You do not need to lock your help center or upgrade to generate a secret.
How It Works
Three-step flow:
Your frontend detects a logged-in user
Your backend generates a signed JWT with user details
Your frontend calls
Ferndesk('identify', { jwt })
The help center and widget now knows who the user is for authentication, personalization and analytics.
The identify method only works from the same domain as your help center or a 1-level subdomain. If your help center is at help.example.com, you can identify from app.example.com but not otherdomain.com.
JWT Secret
Before you can sign JWTs, you need a signing secret from your Ferndesk dashboard:
Go to Help Center > Access Control.
In the User identification section, expand the JWT identify row.
Click Generate Secret.
Generating a secret automatically enables JWT authentication for your help center. The secret is shown only once. Copy it immediately and store it securely in your backend environment variables. You won't be able to view it again.
If you need to rotate the secret, click Regenerate. This replaces the existing secret and invalidates all tokens signed with the old one. Update your backend with the new secret before users are affected.
You can only have one JWT secret per help center. Generate Secret is only available when no secret exists. Regenerate is available when a secret already exists.
Browser sign-in for MCP and AI clients
JWT identify works silently in your product. AI clients sign users in through a browser instead.
In JWT identify under Browser sign-in (MCP and AI clients):
Enter your SSO login page URL. Ferndesk redirects users here with a
return_toparameter. After sign-in, redirect toreturn_towith ajwtquery parameter.Optionally turn on Allow email sign-in for AI clients so existing users can prove email ownership when connecting an AI client. Leave this off if user attributes gate sensitive content.
Click Save changes.
Use a valid http: or https: URL. Invalid values show Enter a valid login page URL, including https://.
Related: Use Ferndesk with AI tools · Let readers use your help center in AI tools
Generate the JWT Server-Side
Create an endpoint that returns a signed token. Ferndesk does not require iss (issuer) or aud (audience) claims.
Required claims:
sub(string, required): Unique ID in your system that identifies this user. This is the primary identity key.email(string, required): User's email addressexp(number, required): Token expiration timestampiat(number, required): Token issuance timestamp
Optional claims:
name(string, optional): Display namecustomAttributes(object, optional): Extra user attributes. You can also usemetadata; both keys are accepted.
The sub claim must remain stable for each user. Ferndesk uses this subject to identify and link users. If a user's sub changes, they will not be linked to their previous help center identity.
Node.js example:
const jwt = require('jsonwebtoken');
app.get('/api/ferndesk-token', async (req, res) => {
if (!req.user) return res.status(401).json({ error: 'Not authenticated' });
const now = Math.floor(Date.now() / 1000);
const token = jwt.sign({
sub: req.user.id,
email: req.user.email,
name: req.user.name,
iat: now,
exp: now + 3600, // 1 hour
customAttributes: { plan: req.user.plan }
}, process.env.FERNDESK_JWT_SECRET, { algorithm: 'HS256' });
res.send(token);
});Python example:
import jwt
import time
@app.route('/api/ferndesk-token')
def ferndesk_token():
if not current_user:
return {'error': 'Not authenticated'}, 401
now = int(time.time())
token = jwt.encode({
'sub': current_user.id,
'email': current_user.email,
'name': current_user.name,
'iat': now,
'exp': now + 3600,
'customAttributes': {'plan': current_user.plan}
}, os.environ['FERNDESK_JWT_SECRET'], algorithm='HS256')
return tokenNever expose your JWT secret in client-side code. Store it in environment variables server-side only.
Call Identify from Your Frontend
Fetch the token from your backend and pass it to the SDK:
Ferndesk('init', { widgetId: 'your-widget-id' });
fetch('/api/ferndesk-token').then(r => r.text())
.then(jwt => Ferndesk('identify', { jwt }))
.catch(err => console.error('Identification failed:', err));React example:
useEffect(() => {
window.Ferndesk('init', { widgetId: 'your-widget-id' });
if (currentUser) {
fetch('/api/ferndesk-token')
.then(r => r.text())
.then(jwt => window.Ferndesk('identify', { jwt }));
}
}, [currentUser]);Call identify after init but before opening the widget. To log out, reinitialize without calling identify.
Verify It's Working
Check these indicators:
Browser console: No errors. Invalid JWTs show
Ferndesk: identify failed - invalid jwtContact form: Email and name will be pre-filled
Analytics: User sessions appear in your dashboard
Common Errors
Ferndesk: identify requires a jwt
Missing jwt parameter. Check that your backend is returning a JWT string.
invalid jwt
Signature verification or claim validation failed. Verify:
Correct JWT secret matches what's stored in Ferndesk
Token hasn't expired
Algorithm is HS256
Required claims are present:
sub,email,exp, andiat
JWT subject does not match the existing help-center user
This error occurs when a user's email already exists in your help center but with a different subject ID. This means someone previously signed in with that email using a different identity system or sub value.
To resolve:
Ensure your backend always sends the same
subfor each userIf you've changed user ID systems, the affected user will need to be re-provisioned in Ferndesk
must be called from same domain or 1-level subdomain
Domain mismatch. Your app and help center must share a root domain.
The sub claim is the primary identifier for users. While email is required, Ferndesk binds identity to the subject, not the email address alone. This prevents account takeover if email addresses change or are reused.
Security Notes
Set token expiration (1 hour is common)
Only generate tokens for authenticated users
Use HTTPS everywhere
Never commit secrets to version control
Keep your
subvalues stable. Ferndesk persists identity by subject.