Skip to content

Linking widget activity to real users gives you accurate attribution in the dashboard, and lets users see their own votes, comments, and submissions when they open the widget.

The widget loads anonymously as soon as Quackback("init") runs — no identify call is required to make it appear. You call identify when you know who the user is, so their activity is attributed to their account instead of to an anonymous session.

Two ways to identify a known user:

  • With their details — the simplest option. Pass id, email, and name directly from the client.
  • With a signed token — sign a short-lived JWT on your server and pass it as ssoToken. Use this in production so nobody can impersonate another account.

If you never call identify, the widget stays anonymous and prompts for an email inline the first time the user posts.


Identify logged-in users

Call identify after login and any time the user's session changes.

Quackback("identify", {
  id: "user_123",
  email: "ada@example.com",
  name: "Ada Lovelace",
});
FieldTypeRequiredNotes
idstringYesStable, unique ID from your system
emailstringYesUsed for notifications and dedup
namestringNoDisplay name shown in the dashboard

Unverified calls trust the client, so a determined user could spoof someone else's identity. For production, turn on Verified identity only and use ssoToken — see below.


Anonymous sessions

Do nothing. The widget loads anonymously after init. If the visitor submits feedback or votes, a session is created on the fly and the widget prompts for an email inline at submission time.

Quackback("init", { placement: "right" });
// That's it — no identify call needed for anonymous visitors.

Anonymous is the default state; there's no { anonymous: true } flag to pass. If they later log in and you call identify, everything they did anonymously moves across to their account automatically.


Bundle identity into init

If you already know the user when the widget loads, pass identity directly to init and skip the separate identify call:

Quackback("init", {
  placement: "right",
  identity: { id: "user_123", email: "ada@example.com", name: "Ada" },
});

The identity key accepts the same shapes as the standalone call — { id, email, name } or { ssoToken }. Omit it entirely for anonymous, or if you'll call Quackback("identify", ...) later (for example, once async auth resolves).


Switch users mid-session

Call identify again with a different user any time the signed-in user changes — account switching, post-login transitions, or profile updates. The widget picks up the new identity and subsequent activity is attributed correctly.

// User A signs out, User B signs in:
Quackback("identify", { id: "user_B", email: "bob@example.com", name: "Bob" });

No need to call logout in between — the new identify call replaces the previous session.


Clear identity on logout

Call logout to clear the current identity. The widget stays visible — just in an anonymous state, same as it started. Put this in your logout handler.

Quackback("logout");

If the widget panel is open, it closes automatically.


Verified identity with JWT

Unverified calls and inline email capture both trust the client, so a determined user could post feedback as a different account. A signed token removes that possibility — Quackback only accepts identities your backend has vouched for.

Turn on Verified identity only in Admin → Settings → Widget and copy the Widget Secret. Once it's on, unverified identify calls and the inline email prompt are both rejected — only a valid token from your server will identify a user.

Never expose the widget secret in client-side code. Sign tokens on your server only.

How it works

Your server signs a JWT with HS256 using the widget secret. The widget sends ssoToken to Quackback, which verifies the signature before accepting the identity claims.

JWT claims

ClaimRequiredNotes
subYesUser's stable ID
emailYesUser's email address
nameNoDisplay name
avatarUrlNoURL of the user's avatar image
expRecommendedUnix timestamp; use ~5 minutes from now
customNoAny additional key-value pairs (see below)

Server-side signing examples

// app/api/widget-token/route.ts
import { SignJWT } from "jose";
import { getServerSession } from "next-auth";
 
const secret = new TextEncoder().encode(process.env.QUACKBACK_WIDGET_SECRET);
 
export async function GET() {
  const session = await getServerSession();
  if (!session?.user) {
    return new Response("Unauthorized", { status: 401 });
  }
 
  const token = await new SignJWT({
    sub: session.user.id,
    email: session.user.email,
    name: session.user.name,
  })
    .setProtectedHeader({ alg: "HS256" })
    .setExpirationTime("5m")
    .sign(secret);
 
  return Response.json({ token });
}

Pass the token to the widget

Fetch the token from your endpoint and pass it as ssoToken.

const { token } = await fetch("/widget-token").then((r) => r.json());
 
Quackback("identify", { ssoToken: token });

Fetch a fresh token on each login or page load. Tokens expire quickly by design — this limits the blast radius if one is intercepted.


Custom attributes

You can pass extra fields like plan, company, or mrr alongside the standard ones and they'll show up on the user's profile. This works for both unverified calls and signed tokens.

With an unverified identify call:

Quackback("identify", {
  id: "user_123",
  email: "ada@example.com",
  name: "Ada Lovelace",
  plan: "pro",
  company: "Acme Corp",
  mrr: 299,
});

Or as extra claims inside a signed JWT:

// Server-side
const payload = {
  sub: user.id,
  email: user.email,
  name: user.name,
  plan: "pro",
  company: "Acme Corp",
  mrr: 299,
};

Attributes are validated against the ones you've configured. Anything that doesn't match a configured attribute is ignored, not rejected — so you can pass extra data without worrying about breaking the call.

Set up your attributes in Admin → Settings → User Attributes first so nothing gets silently dropped.


Rotate the secret

If your secret is ever exposed, rotate it immediately:

  1. Go to Admin → Settings → Widget and click Regenerate Secret.
  2. Deploy updated server-side token signing with the new secret.
  3. The old secret is invalidated instantly — users will see an auth error until your server is updated.

Rotate and deploy atomically. Any lag between invalidating the old secret and deploying the new signing code will cause identify calls to fail for active users.


Next steps