Skip to main content
The widget supports three levels of end-user identity. They differ in one thing: whether the server can trust who the user is.

Anonymous

With no identity set, the widget stores a random visitor id in localStorage so a reload resumes the same conversation in the same browser. Nothing about the user is asserted to the server.

Soft (advisory) identity

Pass a userId to key reload continuity, so the same browser resumes the same conversation:
Soft userId is advisory and is never used for auth or credentials. Any page can set any value, so the server does not bind it into the session token
  • it rides only as client-side session metadata. Do not use it to gate access to per-user data. For that, use verified identity below.

Verified identity (HMAC)

Verified identity proves to Karta that your server vouches for this userId, so the agent can safely act on per-user data. You sign the userId with a per-key secret; the widget forwards the signature; Karta verifies it and binds the verified subject into the short-lived session token.

The scheme

For normal verified identity, compute the token on your server (never in the browser - the secret must not ship to a client):
  • The secret is the identity_verification_secret you generate on the agent’s Embed tab. It is server-only and never shown in a browser.
  • The message is the exact userId string you also pass to the widget.
  • The output is lowercase hexadecimal (a SHA-256 HMAC, 64 hex chars).

Server-side signing

Render the token into the page (it is per-user, short by nature, and safe to expose to that user’s browser - it only proves that user’s id).

Wiring it into the widget

Pass the userId and identityToken together - on the tag:
or via the command API:
The widget forwards user_id + identity_token to the embed token mint (POST /v1/embed/session-tokens). Karta recomputes the HMAC with the constant-time compare and, on a match, mints a session token whose sub is bound to that userId. A bad signature is rejected; a userId with no token stays soft (never bound).
The identityToken is not an auth source on its own - the embed key (or session token) is the credential. The token only upgrades a soft userId to verified. Its validation is entirely server-side.

Telling the agent about your user

A verified identity says who the user is. A context bag says what the agent should know about them, and Karta injects it into the agent’s instructions every turn.
userinfo is the one reserved key: its name and email also drive the widget’s “Signed in as” line. Everything else is yours. A context-bearing token must expire. A plain identity token has no timestamp and stays valid indefinitely, which is fine for “this is user_123” - a fact you re-assert freely. It is not fine for a token that also asserts facts: a captured one would keep vouching for a cancelled plan or a departed employee. So iat, exp and jti are required whenever context is present, and a token carrying context without them is rejected.

Keeping a long conversation alive

Because the token now expires, the widget needs a way to get a new one. Give it identityTokenFn and it will ask for a fresh proof on every mint:
Without it, the widget reuses the one token you passed at init - fine while that token is valid, but once it lapses the conversation can no longer renew its session and stops working. Set exp comfortably longer than your session-token lifetime (default 15 minutes) either way, and under the one-hour ceiling: a context-bearing proof whose exp - iat exceeds 3600 seconds is rejected. If your endpoint is briefly down, the widget falls back to the token you passed at init rather than failing: losing the context is recoverable, losing the session is not.
Put in context only what the agent may see and what you accept being stored with the conversation. Prefer facts your server derived over free text your user typed: the agent reads this like any other input, so treat it as data you are showing the model, not as instructions it will obey.

Turning it on for an agent

Injection is off by default - it costs tokens on every turn, so it is the agent’s choice. Opt in from karta.toml:
Without this the bag is still verified and still drives the sign-in status; it just never reaches the agent’s prompt. Limits: 40KB of JSON, 200 keys, 6 levels deep, 4KB per string, 100 items per array, and roughly 10K tokens once rendered. Over any of them the bag is rejected, not trimmed - a truncated bag could drop a “not” and leave the agent believing the opposite of what you said.
Context needs a session to attach to, so it works on the Managed Agents and Responses transports. The Chat Completions adapter is stateless - it runs each call on a fresh throwaway session - so context is not supported there. It would apply to your first message and silently vanish from the second, which is worse than not offering it.
Context is captured when the session starts and stays fixed for that conversation. If a user upgrades mid-chat, the agent sees it in their next conversation, not this one.

Host-attested step-up

If your app has completed a stronger verification step for the user, your server can mint a structured identity token that carries that fact with the same identity signature. Karta uses this for sensitive approval flows: the browser cannot add stepped_up_at or aal next to a regular HMAC and have it trusted. The structured token format is:
The JSON payload must include the same user_id passed to the widget and may include: The HMAC signs the encoded payload segment:
After the user completes your step-up challenge, call identify again with the new token:
Changing identityToken invalidates the widget’s cached session token, so the next widget request is minted with the fresh signed claims. See the authenticated agent recipe for the full flow.

Backend-minted tokens (alternative)

If you already mint session tokens server-side with a kt_live_... API key, skip the embed key entirely and hand the widget a token function or endpoint. Your backend holds the secret key; the browser only ever sees the short-lived token.
Your endpoint calls POST https://karta.sh/api/agents/:slug/session_tokens with your kt_live_... key and returns the minted token. Because Karta authenticated that server key, the subject you put on the token is authoritative without an HMAC. See Session tokens. The headless client also accepts a refreshable tokenFn (re-called with { force: true } after a 401) - see Headless & React.

Next

Theming & config

Generate and rotate the identity secret on the Embed tab.

Security & privacy

The credential boundaries and the data path.

Authenticated agent

A worked server-side recipe for signed-in users and step-up.