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

# Authenticated agent

> Embed a Karta agent for signed-in users with verified identity, trusted per-user context, and host-attested step-up for sensitive approvals.

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.

<Warning>
  Never put the identity-verification secret in browser code. Only your server
  signs identity tokens.
</Warning>

## Flow

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

<Steps>
  <Step title="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.
  </Step>

  <Step title="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.
  </Step>

  <Step title="Identify before the widget opens a session">
    Fetch the signed identity from your app, call `karta("identify", ...)`, and
    then load or open the widget.
  </Step>
</Steps>

## Server-side identity token

The regular verified-identity token is a lowercase hex HMAC over the user id:

<CodeGroup>
  ```js Node theme={null}
  import crypto from "node:crypto";

  function identityToken(userId) {
    return crypto
      .createHmac("sha256", process.env.KARTA_IDENTITY_SECRET)
      .update(userId)
      .digest("hex");
  }
  ```

  ```python Python theme={null}
  import hashlib
  import hmac
  import os

  def identity_token(user_id: str) -> str:
      return hmac.new(
          os.environ["KARTA_IDENTITY_SECRET"].encode(),
          user_id.encode(),
          hashlib.sha256,
      ).hexdigest()
  ```
</CodeGroup>

Return it from your own authenticated endpoint:

```json theme={null}
{
  "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:

```html theme={null}
<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.

```js theme={null}
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`:

```js theme={null}
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`](https://github.com/karta-sh/examples/tree/main/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.
