All documentation

Identity Delegation — integration guide

Purpose

Identity Delegation lets your ChatSystem agent help a signed-in user with their own data—for example, track an order, check a subscription, or open a case.

Your website stays in control. It continues to sign the user in and decide what they may access. ChatSystem receives only a short, limited proof, and your API checks the permission again before returning any data.

In short: you do not share the password, session cookie, or a key that grants access to your data. You temporarily tell ChatSystem which account is signed in and which actions it may request.

The flow in one minute

1. Browser -> your backend: calls /api/chat-identity with your normal session
2. Your backend -> ChatSystem: requests a short, limited identity
3. Browser -> ChatSystem: sends the question with that in-memory identity
4. ChatSystem -> your business endpoint: sends a signed tool call
5. Your endpoint -> ChatSystem -> browser: returns only the authorized data

The identity proof stays in widget memory and expires quickly. ChatSystem sets its lifetime and also returns expiresAt. You do not choose that duration: the widget requests a fresh identity before expiry. After logout or an account switch, your frontend only needs to report that identity may have changed.

Four things to know

Item What is it for? Where should it live?
App token Identifies the public widget installation. In frontend configuration. It is not a secret.
Mint key Lets your server request a short ChatSystem identity. Only on your server or in your secret manager.
Capability Names one permission, such as orders:read. Computed by your backend, never chosen by the browser.
Short identity Temporarily proves which account is using the widget. In widget memory only.

The guided developer workflow provides your company-specific values and code examples to adapt: open the Identity Delegation setup.

Before you start

You need:

  • a ChatSystem widget already associated with your company;
  • an authorized domain for each website that loads the widget;
  • an existing customer login system: a session cookie, SSO, or another method already used by your application;
  • one server-side HTTPS endpoint for each action the agent may request.

You do not need to replace your login system. Identity Delegation connects to it.

1. Choose the widget and authorize your websites

In step 1 of the guided setup, select the app token for the relevant widget. Then make sure every origin that displays it is listed in Authorized domains.

An origin includes the protocol and hostname, such as https://shop.example.com. This list protects widget loading in general; it does not grant access to a user's data by itself.

2. Create the server-side mint key

In step 2, create a key limited to widget-identity:mint.

Save it immediately in your backend's secure environment, for example as CHATSYSTEM_IDENTITY_API_KEY. Its full value is displayed only once.

This key must never appear:

  • in frontend code or your JavaScript bundle;
  • in an HTML attribute, URL, or tag manager;
  • in logs, analytics, or screenshots;
  • in your Git repository.

3. Add the identity endpoint to your backend

Add a route on your own server, such as GET /api/chat-identity. The browser calls it with your website's normal session.

This route performs five straightforward actions:

  1. read your cookie or session with your existing authentication code;
  2. return HTTP 204 with no identity when nobody is signed in;
  3. read the account's stable internal ID and current permissions from your database;
  4. ask ChatSystem for a short identity using the key created in step 2;
  5. return the { token, expiresAt } response to the widget.

Use an opaque internal identifier for subject, such as a UUID. Do not use an email address, phone number, or value that could later be assigned to someone else.

Express / TypeScript example:

// Your backend file: src/routes/chatIdentity.ts
app.get("/api/chat-identity", async (request, response) => {
  // Your existing function reads your application's cookie or session.
  const user = await readYourExistingSession(request);

  // Nobody is signed in: anonymous chat continues normally.
  if (!user) return response.status(204).end();

  const mintResponse = await fetch(
    `${process.env.CHATSYSTEM_API_URL}/auth/widget-identity/token`,
    {
      method: "POST",
      headers: {
        // Secret created in step 2. This line runs on the server only.
        Authorization: `Bearer ${process.env.CHATSYSTEM_IDENTITY_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        // Public app token selected in step 1.
        appToken: process.env.CHATSYSTEM_APP_TOKEN,
        // Stable internal ID read from your session or database.
        subject: user.stableOpaqueId,
        // Permissions currently granted by your backend to this account.
        capabilities: ["orders:read"],
      }),
    },
  );

  if (!mintResponse.ok) return response.status(502).end();

  // ChatSystem returns { token, expiresAt }. The widget keeps the token in memory.
  return response.json(await mintResponse.json());
});

4. Connect identity to the widget

The identityProvider connects your login interface to the widget. It has two functions:

  • getToken() calls /api/chat-identity. When a protected action requires login, it can open your existing login flow and try again;
  • subscribe() listens for login, logout, and account-switch events. It tells the widget to forget the previous identity.

If you use React and the npm package

// Frontend: src/components/ChatSystemWidget.tsx
import { App, type WidgetIdentityProvider } from "@chatsystem/client";

const readIdentityToken = async (): Promise<string | null> => {
  const response = await fetch("/api/chat-identity", {
    credentials: "include", // Sends your website's secure session cookie.
    cache: "no-store",
  });

  if (response.status === 204 || response.status === 401) return null;
  if (!response.ok) throw new Error("CHAT_IDENTITY_UNAVAILABLE");

  const identity = (await response.json()) as { token?: unknown };
  return typeof identity.token === "string" ? identity.token : null;
};

const identityProvider: WidgetIdentityProvider = {
  async getToken(request) {
    const currentToken = await readIdentityToken();
    if (currentToken || !request.interactive) return currentToken;

    // Connect your existing modal, popup, SSO, or login page here.
    const signedIn = await openYourExistingLogin();
    return signedIn ? readIdentityToken() : null;
  },

  subscribe(identityMayHaveChanged) {
    // Call the callback after login, logout, or an account switch.
    return onYourAuthStateChanged(identityMayHaveChanged);
  },
};

export function ChatSystemWidget() {
  return (
    <App
      appToken="YOUR_APP_TOKEN"
      displayMode="launcher"
      identityProvider={identityProvider}
    />
  );
}

If you use the bundled index.js script

The bundle stays the same. To provide JavaScript functions, load it without data-appToken, then call ChatSystem.mount(). The app token is passed to mount() instead of the <script> tag.

<!-- Frontend: load the usual ChatSystem bundle. -->
<script src="https://chatsystem.s3.eu-west-3.amazonaws.com/index.js"></script>

<!-- Frontend: initialization required to provide identity to the widget. -->
<script>
  const readIdentityToken = async () => {
    const response = await fetch("/api/chat-identity", {
      credentials: "include",
      cache: "no-store",
    });
    if (response.status === 204 || response.status === 401) return null;
    if (!response.ok) throw new Error("CHAT_IDENTITY_UNAVAILABLE");
    const identity = await response.json();
    return typeof identity.token === "string" ? identity.token : null;
  };

  window.ChatSystem.mount({
    // The same public app token used by your regular widget.
    appToken: "YOUR_APP_TOKEN",
    displayMode: "launcher",
    identityProvider: {
      async getToken(request) {
        const currentToken = await readIdentityToken();
        if (currentToken || !request.interactive) return currentToken;

        // Replace this function with your existing login flow.
        const signedIn = await openYourExistingLogin();
        return signedIn ? readIdentityToken() : null;
      },
      subscribe(identityMayHaveChanged) {
        return onYourAuthStateChanged(identityMayHaveChanged);
      },
    },
  });
</script>

5. Protect the business endpoint called by the agent

Next, create the HTTPS endpoint that performs the useful action, for example GET /api/orders/:orderId. ChatSystem calls it from the server with a signed proof; neither the browser nor the model creates that proof.

Your endpoint must verify:

  1. that ChatSystem signed the call for your tool;
  2. that the proof is still valid and has not already been used;
  3. that the expected capability, such as orders:read, is present;
  4. that the identified account may still access the requested resource.

The signature confirms where the request came from. It does not replace your business rule. Even with a valid signature, your database must confirm that the requested order belongs to that account.

Step 5 of the guided setup provides a complete Node example in two tabs: In your endpoint and Node utilities. Copy both parts into your backend. The createNodeDelegatedToolVerifier() function is defined in the utilities tab; it downloads ChatSystem's public keys, verifies the signature, and rejects reuse of the same call.

The part specific to your business stays deliberately small:

// Backend: after the ChatSystem request has been verified.
const order = await database.orders.findById(request.params.orderId);

// The verified subject represents the account signed in on your website.
if (!order || order.customerId !== verifiedInvocation.subject) {
  return response.status(403).json({ error: "ORDER_NOT_ACCESSIBLE" });
}

return response.json({
  status: order.status,
  estimatedDelivery: order.estimatedDelivery,
});

If your backend runs on multiple instances, replay memory must be shared between them. The simple version in the guided setup is suitable for one Node instance; use shared storage before distributing traffic across processes.

6. Register the action as a delegated tool

In Agent tools, create an HTTP tool with delegated identity. Describe:

  • the fixed HTTPS URL of your business endpoint;
  • the HTTP method and expected parameters;
  • an audience unique to your API;
  • the action and required capabilities;
  • the request and response schemas.

A clear description helps the agent choose the tool at the right time. It must not contain secrets or promise actions the endpoint does not perform.

Create or review your company's tools.

7. Enable the tool for an agent

Open My agents, choose the relevant agent, and enable the new tool in its list. An agent may use only tools explicitly attached to its configuration.

Before customer traffic, test at least these flows:

  • signed-out user: anonymous chat works and a protected action offers login before any business API call;
  • authorized user: their own resource is returned;
  • another account: the same resource is denied;
  • missing permission: the endpoint denies the action without revealing data;
  • logout or account switch: the previous identity is no longer used;
  • temporary business-service failure: the agent responds clearly and does not loop on the same call.

What the user sees during login

When the user is signed out, ChatSystem pauses the action before calling your API. Your identityProvider opens your normal login experience.

  • Successful same-page or modal login resumes the request.
  • If your website uses a full-page redirect, ChatSystem can resume the original request after the user returns.
  • If the user cancels, the agent simply explains that sign-in is required.
  • After logout or an account switch, the widget asks for a new identity.

The user should not need to repeat the question after successful login.

Security rules to keep

  • Keep the widget-identity:mint key on your server only.
  • Prefer a same-origin /api/chat-identity endpoint.
  • Keep session cookies Secure and HttpOnly, with a SameSite policy that matches your login flow.
  • Never store the ChatSystem identity in localStorage, sessionStorage, a URL, the DOM, or analytics.
  • Compute capabilities from server-side rules; never trust a list sent by the browser.
  • Always check in your database that the account may still access the resource at call time.

Quick diagnosis

Symptom Useful check
Identity mint returns 401 Check the server key and its widget-identity:mint scope.
Identity mint returns 403 Check that the key and app token belong to the same company.
Login is requested repeatedly Check /api/chat-identity, the subscribe() event, and server time.
The correct user is denied Check the capability, tool audience, and business access rule.
Another user sees the data Disable the tool immediately and fix the ownership check in your API.
The request does not resume after login Check that your login callback completes and reports the identity change.

If the issue continues, give ChatSystem support the test time, relevant company and agent, and observed HTTP status. Never include a key, token, cookie, or customer data in your support request.