Skip to main content
Use this recipe when the widget runs inside your signed-in product and the agent should know which of your users it is helping. Your server signs the user id; the browser forwards that proof; Karta verifies it before minting the short-lived session token the widget uses.
Never put the identity-verification secret in browser code. Only your server signs identity tokens.

Flow

signed-in user -> your app -> /api/karta-identity -> widget identify()
                                      |
                                      v
                         HMAC(identity secret, user id)
1

Create an embed key and identity secret

In the agent’s Embed tab, create a publishable pk_live_... key, add your site origin to the allowlist, and generate an identity-verification secret. Store the secret on your server.
2

Mint an identity token on your server

After your own login has authenticated the user, sign the exact userId string you will pass to the widget.
3

Identify before the widget opens a session

Fetch the signed identity from your app, call karta("identify", ...), and then load or open the widget.

Server-side identity token

The regular verified-identity token is a lowercase hex HMAC over the user id:
import crypto from "node:crypto";

function identityToken(userId) {
  return crypto
    .createHmac("sha256", process.env.KARTA_IDENTITY_SECRET)
    .update(userId)
    .digest("hex");
}
Return it from your own authenticated endpoint:
{
  "userId": "user_123",
  "identityToken": "9f1c...",
  "attributes": { "plan": "pro" }
}
attributes are still advisory metadata. The trusted part is the verified userId because Karta recomputes the HMAC server-side before binding it into the session token.

Widget wiring

Queue identify before the widget opens its first session:
<script>
  window.karta = window.karta || function () {
    (window.karta.q = window.karta.q || []).push(arguments);
  };

  fetch("/api/karta-identity", { credentials: "include" })
    .then((res) => res.json())
    .then((identity) => {
      karta("identify", identity);
    });
</script>

<script
  async
  src="https://cdn.karta.sh/widget/v1/karta.js"
  data-embed-key="pk_live_xxx"
  data-agent="coffeeco/support-bot"
></script>
The widget sends user_id and identity_token to the embed-token mint. If the signature matches, Karta mints a short-lived session token with a trusted subject. If the signature is wrong, minting fails instead of silently downgrading a signed request.

Host-attested step-up

For sensitive approvals, your app can attest that the user recently completed your own step-up challenge. The browser still cannot invent this claim: your server signs it into a structured identity token. The structured token format is:
v2.<base64url-json-payload>.<hmac-hex>
The HMAC signs the encoded payload segment, not the decoded JSON.
import crypto from "node:crypto";

function base64url(value) {
  return Buffer.from(JSON.stringify(value))
    .toString("base64url");
}

function stepUpIdentityToken(userId) {
  const payload = base64url({
    user_id: userId,
    stepped_up_at: Math.floor(Date.now() / 1000),
    aal: "mfa",
  });
  const signature = crypto
    .createHmac("sha256", process.env.KARTA_IDENTITY_SECRET)
    .update(payload)
    .digest("hex");
  return `v2.${payload}.${signature}`;
}
After your step-up endpoint returns the new token, call identify again with the same userId and the new identityToken:
async function refreshKartaAfterStepUp() {
  const identity = await fetch("/api/karta-step-up", {
    method: "POST",
    credentials: "include",
  }).then((res) => res.json());

  karta("identify", identity);
}
Changing the identity token invalidates the widget’s cached session token, so the next widget request is minted with the fresh step-up claims. Karta accepts the step-up only while stepped_up_at is recent and not in the future.

Worked example

See integration/verified-identity-widget for a small server-rendered app that:
  • serves a signed-in demo page;
  • mints verified identity tokens server-side;
  • refreshes the widget identity after a simulated step-up;
  • embeds the hosted Karta widget with no API key in the browser.

Safety checklist

  • The embed key is publishable; the identity secret is server-only.
  • The browser never sends a trusted user id without a matching server signature.
  • A soft userId without identityToken is metadata only.
  • Step-up claims are accepted only from a structured, signed identity token.
  • Sensitive side effects should still use approval cards or first-party handoff, not direct agent execution.