Add a View tickets link in your own app that opens a signed-in customer’s ticket list on your portal — right next to your Contact support link. Show it only when the customer actually has a ticket (open or closed), so people who’ve never contacted you don’t see an empty list. It reuses the same single sign-on handoff as Contact support, plus one quick server-side check.
How it works
Two pieces, both driven by your backend:
- Decide whether to show the link. Your backend asks Essential Support — with your workspace API key — whether this customer has any tickets, and shows the link only if they do.
- Open their tickets. When clicked, it hands the customer off with the same signed token as your Contact support link, but lands them on the ticket list (
next=/) instead of the new-ticket form (next=/new).
These use two different credentials, both from Settings → Integrations: an API key for the ticket-count check, and your signing secret for the SSO handoff. Keep both server-side.
Step 1 — Check whether the customer has tickets
Server-side, call the read-back endpoint for this customer. Match them by email (or by externalId if you set one when linking accounts), and read total from the response. With no status filter, it counts every ticket — open and closed. Use limit=1; you only need the count, and total is the full number regardless of limit.
GET https://{slug}.essential.support/api/tickets?email={email}&limit=1
Authorization: Bearer <your API key>
→ { "total": 3, "tickets": [ … ] } // show the link when total > 0
→ { "total": 0, "tickets": [] } // no tickets (or unknown customer) → hide it
Do this on your server and return only a yes/no to your frontend — never put the API key in client code. And fail soft: if the check errors or times out, just hide the link. A missing View tickets link is harmless; Contact support is unaffected.
const API_KEY = process.env.ESSENTIAL_SUPPORT_API_KEY; // server-side only
const PORTAL_HOST = 'acme.essential.support'; // or support.yourcompany.com
// Your frontend calls this; it returns only a boolean — the key never leaves the server.
app.get('/support/has-tickets', requireLogin, async (req, res) => {
try {
const r = await fetch(
`https://${PORTAL_HOST}/api/tickets?email=${encodeURIComponent(req.user.email)}&limit=1`,
{ headers: { Authorization: `Bearer ${API_KEY}` } }
);
if (!r.ok) return res.json({ hasTickets: false }); // fail soft → hide the link
const { total } = await r.json();
res.json({ hasTickets: total > 0 });
} catch {
res.json({ hasTickets: false });
}
});
Step 2 — Open their tickets via SSO
The link itself is the same signed handoff as Contact support — mint a short-lived HS256 token with your signing secret and redirect to /sso — just with next=/ so it lands on the customer’s ticket list. (See Add a Contact support link for the full token contract and rules.)
import jwt from 'jsonwebtoken';
import { randomUUID } from 'node:crypto';
const SIGNING_SECRET = process.env.ESSENTIAL_SUPPORT_SECRET; // server-side only
// Same as your Contact support route, but next=/ lands on the ticket LIST.
app.get('/support/tickets', requireLogin, (req, res) => {
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 + 120, jti: randomUUID() },
SIGNING_SECRET,
{ algorithm: 'HS256' }
);
res.redirect(`https://${PORTAL_HOST}/sso?token=${encodeURIComponent(token)}&next=/`);
});
If you already have a single /support route for Contact support, you can reuse it and pass the destination through — e.g. /support?next=/ for the list, /support?next=/new for a fresh ticket. Just keep validating next as a relative path on your side; Essential Support also rejects absolute or protocol-relative values.
Put it together in your UI
Show Contact support always; render View tickets only when your check returns true:
<a href="/support">Contact support</a>
<!-- Render this one only when /support/has-tickets returned true -->
<a href="/support/tickets">View tickets</a>
Tips
- Cache the check. Whether someone has a ticket changes rarely — check it once when the menu first opens (or once per session), not on every render.
- Open in a new tab from the click itself. If your handoff mints the token asynchronously, open the tab synchronously inside the click handler and point it at the URL once ready — that avoids popup blockers and, in an installed PWA, opens the portal in the browser instead of taking over your app.
- Two credentials, two places. The API key (Step 1) lives at Settings → Integrations → API keys; the signing secret (Step 2) at Settings → Integrations → Signing secrets. They’re independent — rotate either without touching the other.
- The check names one customer. The read-back requires an
emailorexternalId; it never lists other customers’ tickets, and an unknown customer simply returnstotal: 0.
Related
- Add a Contact support link — the SSO handoff and full token contract
- Read your tickets with the API — the read-back endpoint in full
- Create and manage API keys
- Link customer accounts — set an
externalIdfor stable identity