> ## Documentation Index
> Fetch the complete documentation index at: https://gleanfeed.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Identify customers

> Use server-signed identity so customers can interact in the widget without signing in again.

Signed identity connects a customer signed in to your product with their Glean Feed portal account. Use it when customers should submit, vote, comment, or react without a second sign-in.

<Warning>
  Generate signatures only on your server. Never put the workspace widget secret in browser code, a
  mobile app, source control, or a client-visible environment variable.
</Warning>

## Requirements

* Customer accounts must be enabled in the workspace's portal settings.
* Copy the workspace ID and widget secret from the dashboard.
* Use a stable customer ID from your own system.
* Keep `userId` at 200 characters or fewer, `name` at 120, and `email` at 320.

The same signed customer token can also authorize [browser-direct headless feedback](/headless/feedback#alternative-submit-directly-from-the-browser), but the server boundary below applies whether you use the packaged widget or a custom UI.

## 1. Sign the customer on your server

If email is present, trim and lowercase it, then sign a JSON array containing workspace ID, customer ID, and canonical email. Without email, sign `workspaceId:userId`.

```ts theme={null}
import { createHmac } from "node:crypto";

export function signGleanFeedCustomer({ workspaceId, userId, email, widgetSecret }) {
  const canonicalEmail = email?.trim().toLowerCase();
  const payload = canonicalEmail
    ? JSON.stringify([workspaceId, userId, canonicalEmail])
    : `${workspaceId}:${userId}`;

  return createHmac("sha256", widgetSecret).update(payload).digest("hex");
}
```

The signature covers the workspace ID, customer ID, and email when present. `name` is not part of the signature payload.

## 2. Initialize with the signed customer

Send only the signed customer object—not the secret—to the browser:

```js theme={null}
window.GleanFeed.init({
  workspace: "acme",
  workspaceId: "YOUR_WORKSPACE_ID",
  portalBaseUrl: "https://gleanfeed.com",
  user: {
    userId: "customer-123",
    email: "person@example.com",
    name: "Person Example",
    signature: "SERVER_GENERATED_HEX_SIGNATURE",
  },
});
```

You can also call `await window.GleanFeed.identify(user)` after initialization. Reidentify after the signed-in customer changes.

## 3. Confirm identity

Open the widget and submit a test request or vote. Confirm the interaction appears under the expected customer in the dashboard. In launcher mode, signed customers can also receive an unread [notification badge](/widget/notifications).

If the email matches a pending invite-only board invitation, successful identity grants access to that board automatically.

## Failure behavior

Missing `workspaceId`, disabled customer accounts, invalid or oversized fields, an invalid signature, or a failed request creates no new signed session. A new identification also fails with an identity mismatch if the email belongs to a workspace team member's preserved portal history. An already-established signed customer with the same `userId` and email remains separate and continues to work. For a new customer collision, use a different customer email or omit the email and identify that customer by `userId`; Glean Feed never merges a signed customer into team history.

Free and Starter plans also limit the number of tracked customers. Identifying a customer for the first time returns `403` with `code: "customer_limit"` after the workspace reaches that limit. Existing identified customers continue to work, including in workspaces that are already over the limit. See [customer account limits](/portal/accounts#tracked-customer-limits) for plan amounts and what counts.

The widget falls back to anonymous browsing only when there is no existing embedded portal session; a failed reidentification does not clear an existing session. Use [troubleshooting](/widget/troubleshooting) if the test interaction remains anonymous.
