JWT SSO
Sign your logged-in users into the Feedjolt portal and widget with a short-lived JWT your backend mints. No second login. Configure claims, secrets, rotation.
If your app already knows who the user is, you shouldn't make them log in again to leave feedback. With JWT SSO your backend signs a short-lived token identifying the user; Feedjolt verifies it server-side and transparently signs them into the portal and the in-app widget.
How it works
- You configure a signing secret and algorithm at Dashboard -> Developers -> JWT SSO.
- When a signed-in user opens feedback, your backend mints a JWT with their claims, signed with that secret.
- You hand the token to Feedjolt - either on the widget via
data-sso-token, or by POSTing it to the identify endpoint. - Feedjolt verifies the signature, creates or updates the matching end user, and sets a server-side session cookie. No magic-link, no extra login.
The secret lives only on your server and on Feedjolt's. The browser only ever sees the resulting token - never the secret.
Configuration
JWT SSO is configured per workspace (owner only) at Dashboard -> Developers -> JWT SSO:
| Field | Notes |
|---|---|
secret_key | Your shared signing secret. 16-512 characters. Store it server-side only. |
algorithm | One of HS256 (default), HS384, HS512. HMAC shared-secret signing. |
issuer | Optional. If set, the token's iss claim must match. |
audience | Optional. If set, the token's aud claim must match. |
sync_mode | UPSERT (default) auto-creates unknown users; UPDATE_ONLY rejects tokens for users that don't exist yet. |
Only HMAC algorithms are supported - there is no public-key (asymmetric) mode. Both sides hold the same secret.
Token claims
Mint a standard JWT signed with your secret. These claims are required - a token missing any of them is rejected:
| Claim | Meaning |
|---|---|
sub | Your stable user ID. Used as the end user's external ID. |
email | The user's email. |
name | Display name. |
iat | Issued-at (standard JWT). |
exp | Expiry (standard JWT). Keep it short - minutes, not days. |
Optional claims enrich the synced profile:
| Claim | Meaning |
|---|---|
avatar_url | Profile image URL. |
custom_fields | Object of arbitrary key/values stored on the end user. |
company | Object identifying the user's company - see below. |
If you configured an issuer or audience, also include the matching iss / aud claims; they're only verified when configured.
Company claim
Pass company as an object to attach the user to a company record (created or updated on the fly). companies (an array) is also accepted - the first entry is used.
{
"company": {
"id": "org_123",
"name": "Acme Inc",
"plan": "enterprise",
"mrr": 2400,
"industry": "fintech",
"employee_count": 120
}
}Only id and name are required for a company; the rest are optional.
Minting the token
Mint the JWT on your backend, where the secret is safe. Keep exp short - the token is single-use to bootstrap the session.
// Node.js - npm i jsonwebtoken
import jwt from "jsonwebtoken";
const token = jwt.sign(
{
sub: user.id,
email: user.email,
name: user.fullName,
avatar_url: user.avatarUrl,
company: { id: user.orgId, name: user.orgName },
},
process.env.FEEDJOLT_JWT_SECRET,
{ algorithm: "HS256", expiresIn: "5m" },
);# Python - pip install pyjwt
import datetime, jwt
now = datetime.datetime.now(datetime.timezone.utc)
token = jwt.encode(
{
"sub": user.id,
"email": user.email,
"name": user.full_name,
"avatar_url": user.avatar_url,
"company": {"id": user.org_id, "name": user.org_name},
"iat": now,
"exp": now + datetime.timedelta(minutes=5),
},
FEEDJOLT_JWT_SECRET,
algorithm="HS256",
)Handing the token to Feedjolt
Widget: pass the token on the loader's data-sso-token attribute (see Widget configuration). The widget forwards it and the user is signed in inside the panel.
Portal / direct: POST the token to the identify endpoint for your workspace:
curl -X POST https://api.feedjolt.com/api/{workspace}/identify \
-H "Content-Type: application/json" \
-d '{"token": "YOUR_JWT_HERE"}'On success Feedjolt returns the synced end user and sets a server-side session cookie (httpOnly, secure, SameSite=Lax). That cookie - not your JWT - keeps the user signed in afterward, so you mint a fresh short-lived JWT only at sign-in.
Key rotation
Rotating the signing secret is zero-downtime. When you save a new secret, Feedjolt keeps the previous one valid so tokens already in flight still verify. Up to the last 5 secrets stay accepted during rollover; older ones drop off automatically.
To rotate: set the new secret in the dashboard, deploy it to your backend, and let any in-flight tokens drain. No coordinated cutover needed.
Security notes
- Verification is server-side. Feedjolt validates the signature, expiry, and (if set) issuer/audience on its own servers. Never trust identity asserted by the browser.
- The secret never reaches the browser. Mint tokens on your backend only. Treat the secret like a password.
- Keep
expshort. The JWT only needs to live long enough to bootstrap the session cookie - minutes is plenty. UPDATE_ONLYfor closed systems. If you provision users out of band and never want SSO to create new ones, setsync_modetoUPDATE_ONLY.
MCP Troubleshooting
Fix common Feedjolt MCP errors: 402 plan required, 401 unauthorized, 403 missing scope, 429 rate limited, tag-not-found, and the delete guards on boards and statuses.
Sandbox and trial
How agents and developers try Feedjolt without a sales call: self-serve signup, the Growth trial, and Startup API keys.
