Harnix
Integrate

Authentication

Three identity levels: anonymous publishable key, signed publishable key, your own token.

Every request to the public endpoints (/v2/token, /v2/agents, /v2/conversations, /v2/chat, /v2/runs) authenticates with exactly one of:

  • a chat session token (Authorization: Bearer <token>) — issued to a specific end-user, short-lived;
  • an API key (X-API-Key: <key>) — used when your backend calls directly on behalf of an end-user, see Server-to-server.

This page is about getting a token. There are three identity levels, each with its own way of obtaining one — pick based on whether you want the end-user to have an identity at all, and if so, who's vouching for it.

Three identity levels

1. Anonymous (unsigned pk)

The simplest. The widget generates its own random anonymous_id (a UUID), stores it in localStorage, and sends it along with pk to get a token. You don't need to know who this end-user is. Good for a public FAQ assistant that doesn't need to tie back to a real user account.

<harnix-chat pk="pk_xxxxxxxxxxxxxxxx"></harnix-chat>

No other parameters needed — the widget handles the rest.

2. Identified, signed by you (pk + user-hash)

You know who this end-user is (they're signed into your product, say), but you don't want to expose an API key in the browser. Your backend signs a short-lived HMAC signature and passes it to the widget through three attributes:

<harnix-chat
  pk="pk_xxxxxxxxxxxxxxxx"
  end-user-id="user_42"
  user-hash="a94a8fe5ccb19ba61c4c0873d391e987982fbbd3..."
  expires-at="1735689600"
></harnix-chat>

The widget sends all three fields to POST /v2/token; Harnix verifies the signature with your organization's identity secret (which never leaves your backend). See the signature recipe and code examples on Token exchange.

No silent fallback to anonymous

If you send end-user-id without user-hash or expires-at, the request is rejected (invalid_user_hash) — it does not silently fall back to anonymous. Minting an identified token without a valid signature is something Harnix deliberately refuses.

3. Your own token (token-url or token)

You want full control over token issuance — maybe you have extra logic to run before allowing chat, or you already have an internal token-issuing endpoint. Two options:

The widget POSTs to your endpoint (same-origin, with the current session cookies) whenever it needs a fresh token:

<harnix-chat token-url="/api/harnix-token"></harnix-chat>

That endpoint is yours, not Harnix's — it authenticates the visitor however your product does (a session cookie, typically), then calls POST /v2/token server-side with an API key, and returns { "token": "..." }.

Your token endpoint must authenticate first

Mint a token only for the visitor your own session says is signed in, and only for that visitor's id. An endpoint that hands out tokens without checking the session lets anyone chat as — and read the conversations of — any of your users.

Exactly one of three

pk, token-url, token — the widget requires exactly one of these attributes. Having two (or none) is a configuration error; the widget refuses to render rather than guess.

Origin

Both pk (direct token exchange) and an issued token (every subsequent request) are checked against the Allowed origins configured for your organization (Console → Integration). It's an exact scheme + host + port match, no wildcard subdomains. A mismatched origin gets you origin_not_allowed (403).

Next

  • Token exchange — the full POST /v2/token contract, including how to sign user-hash.
  • Server-to-server — calling with an API key, no widget involved.
  • Errors — what each authentication-related error code means.

On this page