Node.js and Express

Sign Ticketping identity tokens in Node.js with jose, as an Express route.

You’ll add one Express handler that signs a token with jose. jose has no dependencies and runs on Node.js, Bun, Deno and edge runtimes. For Next.js, SvelteKit and other full-stack frameworks, see their install pages.

Install jose

npm install jose

Set the secret

Get the identity secret from Settings → API → Identity secret and set it in your server’s environment. It’s the same value everywhere.

bash
TICKETPING_IDENTITY_SECRET=tpis_...

Add the route

ticketping-token.js:

js
import process from 'node:process'
import { SignJWT } from 'jose'

const secret = new TextEncoder().encode(process.env.TICKETPING_IDENTITY_SECRET)

export async function ticketpingToken(req, res) {
  const user = req.user
  if (!user) return res.sendStatus(401)

  const profile = {}
  if (user.email) profile.email = user.email
  if (user.name) profile.name = user.name

  const token = await new SignJWT(profile)
    .setProtectedHeader({ alg: 'HS256' })
    .setSubject(String(user.id))
    .setExpirationTime('5m')
    .sign(secret)

  res.type('text/plain').send(token)
}

req.user is the signed-in user, as set by Passport or your own session middleware. Mount the handler after that middleware:

js
import { ticketpingToken } from './ticketping-token.js'

app.post('/api/ticketping-token', ticketpingToken)

The handler leaves out email and name when they’re empty, because Ticketping refuses null claims.

Already using jsonwebtoken? It works too: jwt.sign({ sub: String(user.id), email, name }, secret, { algorithm: 'HS256', expiresIn: '5m' }). Leave email and name out of that object when they’re empty.

Call it from the frontend

js
Ticketping('identify', {
  userId: user.id,
  email: user.email,
  name: user.name,
  getToken: async () => {
    const res = await fetch('/api/ticketping-token', { method: 'POST', credentials: 'include' })
    if (!res.ok) throw new Error(`Ticketping token request failed (${res.status})`)
    return res.text()
  }
})

If you use CSRF middleware such as csrf-csrf, add its header to the fetch. For a frontend on another origin, use the full URL and allow the origin with credentials in cors().

Check that it works

  1. Sign in on localhost and run await (await fetch('/api/ticketping-token', { method: 'POST' })).text() in the browser console.
  2. Paste the token into the token validator under Settings → API → Identity secret. It shows the claims, or says exactly what to fix.
  3. Reload and send a message. The dashboard shows it with the user’s name and a Verified badge.

Copy prompt for your AI coding agent

prompt
Add Ticketping identity verification (chat widget v2) to this Node.js / Express server.

Docs index: https://ticketping.com/llms.txt
This page as Markdown: https://ticketping.com/docs/identity-node.md

1. Install jose.
2. Add the ticketpingToken handler exactly as on the docs page and mount it at POST /api/ticketping-token after the
   session/auth middleware: 401 when req.user is missing, otherwise
   new SignJWT({ email, name }).setProtectedHeader({ alg: 'HS256' }).setSubject(String(user.id)).setExpirationTime('5m')
   signed with new TextEncoder().encode(process.env.TICKETPING_IDENTITY_SECRET), sent as text/plain.
   Leave email and name out of the payload when they're empty.
3. Add TICKETPING_IDENTITY_SECRET= to the env example file. Never commit the value or expose it to the browser.
4. In the frontend, call Ticketping('identify', { userId, email, name, getToken }) where getToken POSTs to the route
   (with any CSRF header this app uses) and returns res.text().
5. Tell me how to verify with the token validator at Settings → API → Identity secret.