Astro

Add the chat widget to an Astro site with a script tag in your layout, and an API route for the token.

The script tag is the simplest install for Astro. It needs no framework integration.

Add the script tag

Get your publishable key (pk_...) from Settings → Widgets. It’s public.

src/layouts/Layout.astro, before </body>:

html
<script src="https://widget.ticketping.com/v2/loader.js" data-key="pk_..." async is:inline></script>

is:inline stops Astro from processing the tag, so it stays exactly as written.

Identify signed-in users

Pass the user from the server into an inline script with define:vars. This needs on-demand rendering for the pages that know the user (a server adapter, with prerender = false or output: 'server').

html
---
const user = Astro.locals.user
const ticketpingUser = user
  ? { userId: String(user.id), email: user.email, name: user.name }
  : null
---

<script is:inline define:vars={{ ticketpingUser }}>
  window.Ticketping ||= (...args) => (Ticketping.q ||= []).push(args)

  if (ticketpingUser) {
    Ticketping('identify', {
      ...ticketpingUser,
      getToken: async () => {
        const res = await fetch('/api/ticketping-token', { method: 'POST' })
        if (!res.ok) throw new Error(`Ticketping token request failed (${res.status})`)
        return res.text()
      }
    })
  }
</script>
<script src="https://widget.ticketping.com/v2/loader.js" data-key="pk_..." async is:inline></script>

Astro.locals.user is whatever your auth middleware sets. On your sign-out page, call Ticketping('logout').

If you’d rather use a framework island, mount the React, Svelte or Vue adapter with client:only. Don’t use it in more than one island per page.

Install

The token route uses jose:

npm install jose

Add the token route

src/pages/api/ticketping-token.ts:

ts
import type { APIRoute } from 'astro'
import { SignJWT } from 'jose'

export const prerender = false

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

export const POST: APIRoute = async ({ locals }) => {
  const user = locals.user
  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' } })
}

prerender = false makes it a server endpoint even on a mostly static site. It needs a server adapter such as @astrojs/node, @astrojs/vercel or @astrojs/netlify.

Set the secret

Get the identity secret from Settings → API → Identity secret.

.env:

bash
TICKETPING_IDENTITY_SECRET=tpis_...

The route reads process.env, which works with the Node, Vercel and Netlify adapters. On Cloudflare, read it from locals.runtime.env instead.

Never prefix it with PUBLIC_. Astro bundles PUBLIC_ variables into the browser code.

Check that it works

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

Copy prompt for your AI coding agent

prompt
Add the Ticketping chat widget (v2) to this Astro site.

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

1. In the shared layout, add before </body>:
   <script src="https://widget.ticketping.com/v2/loader.js" data-key="pk_..." async is:inline></script>
   Ask me for the publishable key (starts with pk_). It's public.
2. If the site has signed-in users: in the layout frontmatter, build { userId: String(id), email, name } from
   Astro.locals.user (or this site's auth), pass it with <script is:inline define:vars={{ ticketpingUser }}>,
   add the queue stub window.Ticketping ||= (...args) => (Ticketping.q ||= []).push(args), and call
   Ticketping('identify', { ...ticketpingUser, getToken }) only when there is a user. getToken POSTs to
   /api/ticketping-token and returns res.text(); throw if !res.ok. Call Ticketping('logout') on sign-out.
3. Install jose and create src/pages/api/ticketping-token.ts exactly as on the docs page (prerender = false,
   POST, 401 when signed out, HS256 JWT with sub, email, name, expiration '5m', text/plain).
   Make sure a server adapter is configured.
4. Add TICKETPING_IDENTITY_SECRET= to .env (not committed). Never use a PUBLIC_ 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.