End-user identity
Pass a signed user identity into the widget and Yak will persist conversations against that user. When the same user returns, the widget shows a history pane so they can resume a previous chat or start a new one.
This is opt-in. If you don't pass a user, the widget behaves exactly as it always has — conversations live only in the browser and nothing is persisted server-side.
How it works
- Your backend signs the user id with your application's API secret using
HMAC-SHA256. - The signed identity is passed to
<YakProvider>in the browser. - The widget forwards
{ id, hash }to Yak on every request that touches that user's data. - Yak verifies the hash against the secret, upserts the end-user record, and persists messages against a conversation owned by that user.
The secret never leaves your server. The hash proves the browser is acting on behalf of a real account from your system — without it, anyone with an end-user's id could impersonate them.
This is the same identity-verification model used by Intercom and similar widget vendors. Only the user id is signed, so the identity is tamper-proof.
Set it up
Grab your API secret
Open your application's Customer settings and reveal the End-user identity card. Copy the secret somewhere safe — treat it like any other backend credential.
Store it as an environment variable on your server (never expose it to the browser):
YAK_API_SECRET=...Sign the user id on your server
Compute an HMAC-SHA256 hex digest of the user id using the secret:
import crypto from "node:crypto";
const userHash = crypto
.createHmac("sha256", process.env.YAK_API_SECRET!)
.update(currentUser.id)
.digest("hex");import hmac, hashlib, os
user_hash = hmac.new(
os.environ["YAK_API_SECRET"].encode(),
current_user.id.encode(),
hashlib.sha256,
).hexdigest()require "openssl"
user_hash = OpenSSL::HMAC.hexdigest(
"SHA256",
ENV["YAK_API_SECRET"],
current_user.id,
)import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"os"
)
mac := hmac.New(sha256.New, []byte(os.Getenv("YAK_API_SECRET")))
mac.Write([]byte(currentUser.ID))
userHash := hex.EncodeToString(mac.Sum(nil))Send the hash to the browser however you already pass user data (server-rendered HTML, a session API, etc.).
Pass the identity into the widget
Supply { id, hash } when you set up the widget. React/Next take it as a prop; the other SDKs take it as an option to createYakProvider.
import { YakProvider } from "@yak-io/react"; // or "@yak-io/nextjs/client"
<YakProvider
appId={process.env.NEXT_PUBLIC_YAK_APP_ID!}
user={{
id: currentUser.id,
hash: currentUser.yakHash, // from your server
}}
>
{children}
</YakProvider>import { createYakProvider } from "@yak-io/vue";
const yak = createYakProvider({
appId: "your-app-id",
user: { id: currentUser.id, hash: currentUser.yakHash },
});import { createYakProvider } from "@yak-io/svelte";
const yak = createYakProvider({
appId: "your-app-id",
user: { id: currentUser.id, hash: currentUser.yakHash },
});import { createYakProvider } from "@yak-io/angular";
const yak = createYakProvider({
appId: "your-app-id",
user: { id: currentUser.id, hash: currentUser.yakHash },
});import { createYakProvider } from "@yak-io/nuxt";
const yak = createYakProvider({
appId: "your-app-id",
user: { id: currentUser.id, hash: currentUser.yakHash },
});That's it. Open the widget and you'll see a new history icon in the header — click it to browse past conversations or start a new one.
Updating the user (login / logout)
Your end-user may sign in or out without a full page reload. Keep the widget's identity in sync so conversations follow the right account.
- React / Next.js are declarative — render
<YakProvider>with the newuserprop (or omit it / passundefinedon logout) and the provider re-threads the identity for you. - The Vue / Svelte / Angular / Nuxt SDKs return a handle from
createYakProvider; callsetUser()from your own reactivity (a Vuewatch, Svelte$effect, or Angulareffect) whenever the signed-in user changes. Passundefinedto log out.
import { watch } from "vue";
watch(currentUser, (u) => {
yak.setUser(u ? { id: u.id, hash: u.yakHash } : undefined);
});$effect(() => {
yak.setUser($currentUser ? { id: $currentUser.id, hash: $currentUser.yakHash } : undefined);
});// `currentUser` is a signal; re-run whenever it changes
effect(() => {
const u = currentUser();
yak.setUser(u ? { id: u.id, hash: u.yakHash } : undefined);
});import { watch } from "vue";
watch(currentUser, (u) => {
yak.setUser(u ? { id: u.id, hash: u.yakHash } : undefined);
});Switching to a different id (or logging out) rotates the stored session token, so one user's session is never reused under another account. The first sign-in after anonymous browsing keeps the session, so the visitor's pre-login conversation is claimed by their new account.
The user prop
| Field | Type | Required | Description |
|---|---|---|---|
id | string | Yes | Stable user id from your system. HMAC-protected. |
hash | string | Yes | Hex HMAC-SHA256 of id signed with your application's API secret. |
What the end-user sees
When user is set and the hash verifies:
- A clock icon appears in the chat header.
- Clicking it opens a slide-over panel listing conversations, most recently started first.
- Selecting a conversation re-hydrates the chat with the persisted messages — the user can keep talking right where they left off.
- A "New" button starts a fresh conversation without losing the old ones.
- Each conversation has a trash icon for removing it (and its messages) permanently.
When user is not set, none of this UI shows and no data is persisted.
Anonymous mode
If you don't pass user, the widget works exactly as before:
- Conversations live only in the browser.
- Nothing is written to Yak's database against an identifiable user.
- The history pane is hidden.
You can mix the two modes inside the same application — for example, anonymous chat on a public marketing page and signed-in chat in your dashboard.
Rotating the secret
If you suspect the secret has leaked, rotate it from the dashboard. Rotation invalidates every previously signed hash, so deployed integrations will fail until you re-deploy with the new value. Plan the rotation alongside a deploy of your server code.
To force every current visitor onto a fresh session without touching your identity signing (no redeploy needed), use Sign out all sessions instead — see Session revocation.
Security model
- What's signed: only the user
id. A holder of the secret can produce a valid identity for any user id, so keep the secret on the server. - Where verification happens: every Yak endpoint that touches user data (
/api/chatwhenuseris present, the voice session and transcript endpoints, and the conversation history endpoints) re-verifies the hash and the user's ownership of the conversation. There are no session cookies — the signed identity rides along on each request.
For the broader security context, see the Security page.