Add a Contact Support link in your app that opens a new ticket

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:

Rules you must follow:

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`);
});

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:

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

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.