React Router
Add the chat widget to a React Router v7 app (framework mode), with a resource route for the token.
This guide uses React Router v7 in framework mode (the successor to Remix). You’ll add the provider in root.tsx and a resource route with an action that signs the token.
Install
npm install @ticketping/chat-widget@next josev2 is on the next tag until 2.0.0 is released.
Add the provider
Load the signed-in user in the root loader and pass it to the provider. Get your publishable key (pk_...) from Settings → Widgets. It’s public.
app/root.tsx:
import { Outlet } from 'react-router'
import { TicketpingProvider } from '@ticketping/chat-widget/react'
import { getUser } from '~/auth.server'
import type { Route } from './+types/root'
export async function loader({ request }: Route.LoaderArgs) {
const user = await getUser(request)
return { user: user ? { id: String(user.id), email: user.email, name: user.name } : null }
}
async function getToken() {
const res = await fetch('/api/ticketping-token', { method: 'POST' })
if (!res.ok) throw new Error(`Ticketping token request failed (${res.status})`)
return res.text()
}
export default function App({ loaderData }: Route.ComponentProps) {
return (
<TicketpingProvider publishableKey="pk_..." user={loaderData.user} getToken={getToken}>
<Outlet />
</TicketpingProvider>
)
}Keep your existing Layout, links and ErrorBoundary exports. getUser stands for your app’s session lookup.
The provider follows the user prop: it identifies on sign-in, switches users cleanly, and logs out when user becomes null.
The React adapter ships with 2.0. If a prop name here doesn’t match your installed beta, check the package README; the core JavaScript API works the same in every version.
Add the token route
app/routes/api.ticketping-token.ts:
import { SignJWT } from 'jose'
import { getUser } from '~/auth.server'
import type { Route } from './+types/api.ticketping-token'
const secret = new TextEncoder().encode(process.env.TICKETPING_IDENTITY_SECRET)
export async function action({ request }: Route.ActionArgs) {
const user = await getUser(request)
if (!user) return new Response('Unauthorized', { status: 401 })
const profile: Record<string, string> = {}
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)
return new Response(token, { headers: { 'Content-Type': 'text/plain' } })
}Register it in app/routes.ts:
import { type RouteConfig, index, route } from '@react-router/dev/routes'
export default [
index('routes/home.tsx'),
route('api/ticketping-token', 'routes/api.ticketping-token.ts')
] satisfies RouteConfigIf you use file-system routing (flatRoutes()), the file name already maps to /api/ticketping-token.
Set the secret
Get the identity secret from Settings → API → Identity secret.
.env:
TICKETPING_IDENTITY_SECRET=tpis_...Use the same value in every environment. Never prefix it with VITE_.
Check that it works
- Run
npm run dev, sign in, and open the browser console. - Run
await (await fetch('/api/ticketping-token', { method: 'POST' })).text()and copy the token. - Paste it into the token validator under Settings → API → Identity secret. It shows the claims, or says exactly what to fix.
- Reload and send a message. The dashboard shows it with the user’s name, a Verified badge and a TEST tag.
useTicketping() from @ticketping/chat-widget/react gives your components the same helpers as Next.js, including client for the rest of the API. user may use id or userId.
Copy prompt for your AI coding agent
Add the Ticketping chat widget (v2) to this React Router v7 (framework mode) app.
Docs index: https://ticketping.com/llms.txt
This page as Markdown: https://ticketping.com/docs/react-router.md
1. Install @ticketping/chat-widget@next and jose.
2. In app/root.tsx, return the signed-in user as { id, email, name } (or null) from the root loader using this
app's existing session code, and wrap <Outlet /> in
<TicketpingProvider publishableKey="pk_..." user={loaderData.user} getToken={getToken}> from '@ticketping/chat-widget/react'.
getToken POSTs to /api/ticketping-token and returns res.text(); throw if !res.ok.
Ask me for the publishable key (starts with pk_). It's public.
3. Create app/routes/api.ticketping-token.ts with an action exactly as on the docs page, adapting only the user lookup:
401 when signed out, otherwise a text/plain JWT from jose,
new SignJWT({ email, name }).setProtectedHeader({ alg: 'HS256' }).setSubject(String(user.id)).setExpirationTime('5m'),
signed with new TextEncoder().encode(process.env.TICKETPING_IDENTITY_SECRET). Register it in app/routes.ts if needed.
4. Add TICKETPING_IDENTITY_SECRET= to .env (not committed). Never use a VITE_ prefix for it.
5. Tell me how to verify: sign in on localhost, fetch a token from the console, and paste it into the
token validator at Settings → API → Identity secret.