Add a Contact support link in your own app that drops a signed-in customer straight into a new ticket — with no separate password and no login screen for you to build. Your backend signs a short-lived token vouching for the user; Essential Support verifies it, creates a portal session, and opens the new-ticket form already authenticated as that customer. This is the same mechanism Essential Support’s own “Contact support” menu uses to hand agents into our help desk.
How it works
When a signed-in user clicks your Support link, your backend mints a short-lived HS256 JWT and redirects the browser to your portal’s /sso endpoint with next=/new. Essential Support verifies the signature, creates the customer’s portal session, strips the token from the address bar, and lands them on the new-ticket form.
Your app --(user clicks Support)--> your backend signs a JWT
<-------------------------- 302 redirect to
https://{slug}.essential.support/sso?token=<JWT>&next=/new
The endpoint
Redirect the browser to GET https://{slug}.essential.support/sso?token=<jwt>&next=<path> on your own portal host (your {slug}.essential.support subdomain or a custom domain). next is optional and defaults to /; use /new to open the new-ticket form. next must be a relative path — absolute or protocol-relative URLs are rejected by the open-redirect guard.
The token
Sign an HS256 JWT with your workspace signing secret. Its claims:
email(required) — the user’s email; treated as verified because your signature vouches for it.iat(required) — issued-at, in seconds since the epoch.exp(required) — expiry; keep it short, about 2 minutes afteriat.jti(required) — a unique id per token (e.g. a UUID); recorded to reject replays.sub(optional, recommended) — your stable user id; used as the primary identity key, so a user’s email can change without creating a duplicate.name(optional) — display name.iss(optional) — your workspace slug or id; if present it must match, as defense-in-depth.locale(optional) — the user’s language (e.g.es).
Rules you must follow:
- HS256 only —
alg: noneand asymmetric algorithms are rejected (the token header cannot choose the algorithm). - Sign server-side only. Never put the signing secret in client JavaScript, a mobile bundle, or a public repo — anyone holding it can mint sessions for your users.
- Keep
expshort; the token only has to survive the redirect. Tokens older than ~2 minutes are rejected (about 60 seconds of clock skew is allowed). - Make
jtiunique per token — it is recorded and any reuse is rejected. - Redirect over HTTPS only.
Where to get the signing secret
As the account owner, generate your workspace signing secret at Settings → Integrations → Signing secrets — click Generate new secret. It is shown exactly once — copy it into your backend’s secret store immediately, because it is never displayed again. You can hold two secrets at once to rotate without downtime: generate the new secret, deploy your backend with it, then retire the old one with a grace period during which tokens signed by either secret still verify. You can also generate a sample token and validate it against this secret in the Sandbox.
Sign the token (Node)
import jwt from 'jsonwebtoken';
import { randomUUID } from 'node:crypto';
const SIGNING_SECRET = process.env.ESSENTIAL_SUPPORT_SECRET; // server-side only
const PORTAL_HOST = 'acme.essential.support'; // or support.yourcompany.com
// A "Contact support" link in your app points at this backend route.
app.get('/support', requireLogin, (req, res) => {
const now = Math.floor(Date.now() / 1000);
const token = jwt.sign(
{
sub: req.user.id, // your stable user id (preferred)
email: req.user.email,
name: req.user.name,
iat: now,
exp: now + 120, // 2 minutes
jti: randomUUID()
},
SIGNING_SECRET,
{ algorithm: 'HS256' }
);
res.redirect(`https://${PORTAL_HOST}/sso?token=${encodeURIComponent(token)}&next=/new`);
});
Place the link in your app
In your UI, the Support control simply links to that backend route — the token is minted server-side on each click:
<a href="/support">Contact support</a>
Point next at any relative portal path: next=/new for a fresh ticket, or next=/tickets/{ref} to open an existing one. Absolute URLs are rejected.
If a token is rejected
A bad token never dead-ends the user: Essential Support redirects to the portal sign-in with ?sso_error=1, where the customer can still get in with a magic link. Common causes:
- Invalid signature — wrong secret, or you rotated and deployed a mismatched one.
- Expired or too old —
exphas passed, or the token is older than ~2 minutes. Keepexpshort and sync your server clock (~60s skew is allowed). - Reused token — the same
jtiwas presented twice. Generate a freshjtifor every redirect; don’t cache tokens.
Every accept and reject is recorded in your workspace’s auth audit log with a reason, so you can tell exactly why a token was refused.
Security notes
- Keep
expshort (~2 minutes) and mint one token per user per click. - Use a unique
jtievery time — replay is blocked server-side. - Redirect over HTTPS; keep the signing secret server-side only.
- Customers are not single-seated, so the same customer can be signed in on more than one device via SSO.
Under the hood this is identical to Essential Support’s own “Contact support” menu: the agent console signs a short-lived JWT against the same /sso contract and hands the agent into tickets.essential.support.
Related
- Add a View tickets link — show customers their existing tickets
- Test your integration in the Sandbox — generate and validate SSO tokens
- Link customer accounts
- Create a ticket with the API