Vue and Nuxt

Add the chat widget to a Vue 3 or Nuxt app, with a Nuxt server route for the token.

The Vue plugin loads the widget and gives your components reactive state. You tell it who’s signed in with identify and logout.

Install

npm install @ticketping/chat-widget@next

v2 is on the next tag until 2.0.0 is released. For the Nuxt token route, also install jose.

Vue

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

src/main.ts:

ts
import { createApp } from 'vue'
import { TicketpingPlugin } from '@ticketping/chat-widget/vue'
import App from './App.vue'

createApp(App).use(TicketpingPlugin, { publishableKey: 'pk_...' }).mount('#app')

Then follow the signed-in user, wherever your app keeps it:

src/App.vue:

vue
<script setup lang="ts">
import { watch } from 'vue'
import { useTicketping } from '@ticketping/chat-widget/vue'
import { useCurrentUser } from './auth'

const ticketping = useTicketping()
const user = useCurrentUser()

async function getToken() {
  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()
}

watch(
  user,
  (next, previous) => {
    if (next) ticketping.identify({ userId: String(next.id), email: next.email, name: next.name, getToken })
    else if (previous) ticketping.logout()
  },
  { immediate: true }
)
</script>

<template>
  <RouterView />
</template>

Call logout only when a user signs out, not for visitors who were never signed in: it starts a new anonymous visitor.

For a Vue app without its own server, add the token route to your backend: Django, FastAPI, Flask, Express, Rails, Go or Laravel.

Nuxt

Register the plugin on the client only.

plugins/ticketping.client.ts:

ts
import { TicketpingPlugin } from '@ticketping/chat-widget/vue'

export default defineNuxtPlugin((nuxtApp) => {
  nuxtApp.vueApp.use(TicketpingPlugin, { publishableKey: 'pk_...' })
})

Follow the user in app.vue with the same watch as above. With nuxt-auth-utils, const { user } = useUserSession() gives you the user ref. Run the watch inside onMounted, or guard it with import.meta.client, so it only runs in the browser.

Token route

server/api/ticketping-token.post.ts:

ts
import { SignJWT } from 'jose'

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

export default defineEventHandler(async (event) => {
  const { user } = await requireUserSession(event)

  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)

  setResponseHeader(event, 'Content-Type', 'text/plain')
  return token
})

requireUserSession comes from nuxt-auth-utils and answers 401 when nobody is signed in. With another auth module, swap in its session helper. Because the file ends in .post.ts, Nuxt only routes POST requests to it.

The Vue adapter ships with 2.0. If a name here doesn’t match your installed beta, check the package README; the core JavaScript API works the same in every version.

Set the secret

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

.env:

bash
TICKETPING_IDENTITY_SECRET=tpis_...

Don’t add it to runtimeConfig.public, and don’t prefix it with NUXT_PUBLIC_ or VITE_.

Check that it works

  1. Run the dev server, 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.

Use the widget from your components

vue
<script setup lang="ts">
import { useTicketping } from '@ticketping/chat-widget/vue'

const ticketping = useTicketping()
</script>

<template>
  <button @click="ticketping.open()">
    Help<span v-if="ticketping.unreadCount > 0"> ({{ ticketping.unreadCount }})</span>
  </button>
</template>

useTicketping() returns unreadCount and isOpen as numbers that stay up to date in the template, plus every method of the JavaScript API. They are not refs, so don’t read .value. Plugin options match the init options in Configuration. identify takes userId, not id.

Copy prompt for your AI coding agent

prompt
Add the Ticketping chat widget (v2) to this Vue / Nuxt app.

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

1. Install @ticketping/chat-widget@next (and jose for a Nuxt token route).
2. Register TicketpingPlugin from '@ticketping/chat-widget/vue' with { publishableKey: 'pk_...' }:
   in src/main.ts for Vue, or in plugins/ticketping.client.ts for Nuxt. Ask me for the publishable key (starts with pk_).
3. In the root component, watch this app's signed-in user: when there is a user, call
   useTicketping().identify({ userId: String(id), email, name, getToken }); when a user signs out, call logout().
   Don't call logout for visitors who were never signed in. getToken POSTs to /api/ticketping-token and returns
   res.text(); throw if !res.ok. In Nuxt, run this only on the client.
4. Token route: in Nuxt, create server/api/ticketping-token.post.ts exactly as on the docs page (jose, HS256,
   { sub: String(user.id), email, name }, expiration '5m', secret from process.env.TICKETPING_IDENTITY_SECRET,
   401 when signed out). For plain Vue, add POST /api/ticketping-token to the backend using
   https://ticketping.com/docs/authentication.md.
5. Add TICKETPING_IDENTITY_SECRET= to the server env file (not committed). Never expose it as public runtime config.
6. 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.