Identify users in the widget
The widget stays hidden until you call identify(). This gives you full control over when the widget appears and how sessions are created. You can identify logged-in users with their account details, or show the widget to anonymous visitors.
Identify logged-in users
After your app's auth resolves, call identify with the user's details:
import { useEffect } from "react";
import { useAuth } from "@/hooks/use-auth";
export function WidgetIdentify() {
const { user } = useAuth();
useEffect(() => {
if (user) {
Quackback("identify", {
id: user.id,
email: user.email,
name: user.name,
});
} else {
Quackback("identify", { anonymous: true });
}
}, [user]);
return null;
}| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Stable user ID from your system |
email | string | Yes | User's email address |
name | string | No | Display name shown on posts and votes |
hash | string | No | HMAC hash for verification (see below) |
The id field should be a stable identifier from your system (database ID, auth provider ID, etc.), not an email address. Emails can change, but the ID ties the widget identity to the correct Quackback user.
Anonymous users
Show the widget to visitors who aren't logged in by passing { anonymous: true }:
Quackback("identify", { anonymous: true });This creates an anonymous session immediately, so the widget is ready to use as soon as it appears. What anonymous users can do depends on your anonymous permissions settings:
| Permission | Default | Description |
|---|---|---|
| Anonymous voting | On | Anonymous visitors can vote on posts |
| Anonymous commenting | Off | Anonymous visitors can comment on posts |
| Anonymous posting | Off | Anonymous visitors can submit new posts |
A common pattern is to identify logged-in users with their details and fall back to anonymous for everyone else. This way the widget is always visible, but logged-in users get their feedback attributed.
Clear identity on logout
Pass null to clear the session and hide the widget when a user logs out:
Quackback("identify", null);If the widget panel is open, it closes automatically before hiding the trigger button.
HMAC verification
Basic identification trusts the client. A technically savvy user could call Quackback("identify") with someone else's ID to spoof their identity. HMAC verification prevents this by requiring a server-generated hash that proves the identity is legitimate.
Enable HMAC verification
- Go to Admin → Settings → Widget
- Toggle HMAC verification on
- Copy the widget secret (masked by default, always viewable from this page)
- Add the secret to your server's environment as
QUACKBACK_WIDGET_SECRET
Generate the hash server-side
Compute an HMAC-SHA256 of the user's ID using your widget secret. Here are examples for common frameworks:
Next.js
// app/api/widget-hash/route.ts
import crypto from "crypto";
import { NextResponse } from "next/server";
import { auth } from "@/lib/auth";
export async function POST() {
const session = await auth();
if (!session?.user) {
return NextResponse.json({}, { status: 401 });
}
const hash = crypto
.createHmac("sha256", process.env.QUACKBACK_WIDGET_SECRET!)
.update(session.user.id)
.digest("hex");
return NextResponse.json({ hash });
}Express
// widget.js
import crypto from "crypto";
app.post("/api/widget-hash", (req, res) => {
// req.user set by your auth middleware
const hash = crypto
.createHmac("sha256", process.env.QUACKBACK_WIDGET_SECRET)
.update(req.user.id)
.digest("hex");
res.json({ hash });
});Django
# views.py
import hmac, hashlib
from django.conf import settings
from django.http import JsonResponse
from django.contrib.auth.decorators import login_required
@login_required
def widget_hash(request):
digest = hmac.new(
settings.QUACKBACK_WIDGET_SECRET.encode(),
str(request.user.id).encode(),
hashlib.sha256,
).hexdigest()
return JsonResponse({"hash": digest})Rails
# widget_controller.rb
class Api::WidgetController < ApplicationController
before_action :authenticate_user!
def identify_hash
digest = OpenSSL::HMAC.hexdigest(
"sha256",
ENV["QUACKBACK_WIDGET_SECRET"],
current_user.id.to_s,
)
render json: { hash: digest }
end
endLaravel
// WidgetController.php
use Illuminate\Http\Request;
class WidgetController extends Controller
{
public function identifyHash(Request $request)
{
$hash = hash_hmac(
"sha256",
$request->user()->id,
config("services.quackback.widget_secret"),
);
return response()->json(["hash" => $hash]);
}
}Pass the hash to the widget
Fetch the hash from your server and include it in the identify call:
import { useEffect } from "react";
import { useAuth } from "@/hooks/use-auth";
export function WidgetIdentify() {
const { user } = useAuth();
useEffect(() => {
if (user) {
fetch("/api/widget-hash", { method: "POST" })
.then((res) => res.json())
.then(({ hash }) => {
Quackback("identify", {
id: user.id,
email: user.email,
name: user.name,
hash,
});
});
} else {
Quackback("identify", { anonymous: true });
}
}, [user]);
return null;
}Never expose your widget secret in client-side code. The HMAC hash must be computed on your server.
Rotate the secret
If your widget secret is compromised, rotate it from Admin → Settings → Widget. Click Regenerate to create a new secret. Update QUACKBACK_WIDGET_SECRET on your server immediately - existing identify calls with the old hash will fail once verification is enabled with the new secret.
Next steps
- Install the widget - Embed snippet and SDK command reference
- Widget overview - Configuration and behavior reference