Chat Components

App Credentials

Copy page

How app credentials authenticate end-users, the session token flow, domain validation, and Proof-of-Work protection.

App credentials are the auth mechanism for chat components. This page covers the underlying auth flow, security model, and advanced configuration.

How Authentication Works

When your widget loads, it authenticates through a two-step flow:

  1. Get a session token — the widget calls POST /run/auth/apps/{appId}/anonymous-session. The server validates the request Origin against the app's allowed domains and returns a JWT.
  2. Use the token — subsequent chat requests include the JWT and App ID:
    Authorization: Bearer <session_token>
    X-Inkeep-App-Id: <app_id>

Each session gets a unique anonymous user ID (anon_<uuid>), enabling per-user conversation history.

Create an App via API

curl -X POST "https://your-api.example.com/manage/tenants/{tenantId}/projects/{projectId}/apps" \
  -H "Authorization: Bearer $MANAGE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Docs Chat Widget",
    "type": "web_client",
    "defaultAgentId": "your-agent-id",
    "config": {
      "type": "web_client",
      "webClient": {
        "allowedDomains": ["docs.example.com"]
      }
    }
  }'

See the Apps API Reference for the full CRUD API.

Proof-of-Work Protection

When Proof-of-Work (PoW) is enabled on the server, clients must solve a computational challenge before requesting a session token. This protects against automated abuse.

Note
Note

PoW is optional and controlled by the server administrator via the INKEEP_POW_HMAC_SECRET environment variable. If PoW is not enabled, the challenge endpoint returns 404 and clients skip this step.

Client Integration

Install the solver library:

npm install altcha-lib

Fetch a challenge, solve it, and include the solution when requesting a session token:

import { solveChallenge } from "altcha-lib";

const BASE_URL = "https://your-api.example.com";

async function getPowHeaders(): Promise<Record<string, string>> {
  const res = await fetch(`${BASE_URL}/run/auth/pow/challenge`);

  if (res.status === 404) {
    return {}; // PoW not enabled — skip
  }

  const challenge = await res.json();
  const { promise } = solveChallenge(
    challenge.challenge,
    challenge.salt,
    challenge.algorithm,
    challenge.maxnumber,
  );
  const solution = await promise;

  const payload = btoa(JSON.stringify({
    algorithm: challenge.algorithm,
    challenge: challenge.challenge,
    number: solution?.number,
    salt: challenge.salt,
    signature: challenge.signature,
  }));

  return { "X-Inkeep-Challenge-Solution": payload };
}

Include the returned headers in the anonymous session request:

const powHeaders = await getPowHeaders();

const response = await fetch(`${BASE_URL}/run/auth/apps/${APP_ID}/anonymous-session`, {
  method: "POST",
  headers: powHeaders,
});

if (!response.ok) {
  const error = await response.json();
  throw new Error(error.error?.message || "Session request failed");
}

const { token } = await response.json();

Security Model

FeatureDetails
Domain allowlistOrigin header validated against the app's allowedDomains at token issuance
Scoped accessEach app is bound to a default agent via defaultAgentId
Anonymous identityEach session gets a unique user ID for per-user conversation history
Token expirySession tokens have a configurable TTL (default: 30 days)
PoWOptional Proof-of-Work challenges prevent automated abuse

App Credentials vs API Keys

App CredentialsAPI Keys
Use caseBrowser / client-sideServer-to-server
Exposed to end-usersYes (App ID only)No (secret)
Domain restrictionsYesNo
Per-user identityYes (anonymous sessions)No
Default agentOne agent (via defaultAgentId)One agent per key