Skip to main content
A chat front-end authenticates to TrustGate with one API key and serves many people. Without help, every request from it carries the same application and the same principal, so Activity, Analytics and your cost reports can only say “this application” — never “this person”. End-user attribution closes that gap. A client tells TrustGate who it was serving, and that person lands on the trace, in Activity, and in the event stream alongside the credential that actually authenticated.
Attribution, not authentication. These values are asserted by whoever holds the API key and are never verified by the gateway. They never become the request’s principal, never reach policy decisions, and never grant access. They are for telemetry, analytics and cost attribution only. The authenticated principal is recorded separately and stays unchanged — see Not an identity.When you need the person to be verified, have the client forward each user’s own identity-provider token instead of a shared key — see Verified attribution with SSO.

Nothing to configure on TrustGate

There is no per-tenant setup, no flag on the application and no gateway setting. TrustGate reads the headers below on every request; a request that declares nobody behaves exactly as it did before.

Ways to declare the end user

TrustGate takes the first source that names somebody: TrustGate’s own namespace comes first on purpose: if you set X-TG-*, you did so deliberately, so a request carrying both yours and a vendor’s is attributed the way you asked. The body user field is a last resort — it is one opaque id, not a person — and is read only when no header named anyone. You do not have to send all four fields. Any one of id, email or name is enough; a header set where every value is blank is skipped, and the next source is tried.

Clients TrustGate recognizes

Some clients already forward the signed-in user in their own headers. TrustGate reads those conventions as they are, so the client needs no change and TrustGate no setup: A signed token from a client is read for its claims; its signature is not checked, because the secret belongs to the client. It is recorded under the same rules as any other declaration.

Your own client

Any client — a backend, a custom UI, a proxy — can attribute with TrustGate’s namespace:

The OpenAI user field

A client that already sets the OpenAI API’s user field needs no headers at all:
This is read only when no header declared anyone, and it can only carry an id — no email, name or role.

Verified attribution with SSO

Everything above is declared by the client. To attribute traffic to a person the gateway has verified, the client sends each user’s own identity-provider token as Authorization: Bearer, in place of a shared API key. TrustGate validates it — signature against the IdP’s keys, issuer, expiry, audience, and the application’s allowed client ids — and records the person it names as the request’s principal. Configuring the provider is covered in Authentication. This needs a client that signs users in with your IdP and can forward their token, and a token issued for the audience TrustGate expects. Open WebUI does both with its OAuth connection. The principal is recorded as principal_subject (the token’s sub) and principal_email: the token’s email claim or, when the IdP sends none, the first of emailAddress, preferred_username, upn and unique_name that is an address. Unlike a declared user, the principal reaches policies: guardrail rules that match on the user see this person. A forged or expired token is rejected with 401; it never attributes anything.

Grouping a conversation

A chat client sends each message as its own request. Without a conversation id every message looks like a fresh session, which makes session-scoped alerts and any “conversation” view useless. TrustGate takes the first of these that carries a valid id. An id the client sent — a header or the configured body field — always wins over one the gateway infers:
  1. The session header configured on the gateway (X-Session-Id by default)
  2. A header a known client already sends:
  3. Any other header named x-<vendor>-session-id
  4. The body field named by the gateway’s session configuration, if set
  5. On OpenAI Responses requests, the conversation id (Conversations API)
  6. On OpenAI Responses requests, the conversation of the response named in previous_response_id — so a chain of turns stays one conversation even when the client sends no id
  7. A generated id
The value is recorded as session_id and echoed back on the response as X-Session-Id. The first three headers and the configured header and body field accept 1–256 characters. The rest accept 8–256 characters of letters, digits and . _ : -: a header that only shares a name, or a short placeholder, must not merge unrelated traffic into one session. A generated id stands for a whole conversation only on OpenAI Responses, where it is the id of the chain of turns. On every other API it is a single request: it is echoed so a client can adopt it and send it back, but nothing records it as a session.
session_id contains an underscore, which ingress-nginx drops unless enable-underscores-in-headers: "true" is set in its ConfigMap. Without it, Codex CLI requests fall through to the next source.
Sessions are enabled by default; if a gateway sets session.enabled: false, none of these sources are read.

Where it shows up

Activity. The User column shows the declared person instead of the API key’s principal. Because the name is unverified it is marked as such, and opening a request shows who declared it and, beside it, the credential that authenticated — both facts at once. When the declared email is the verified principal’s — a client forwarding both the user’s IdP token and its own user headers — the user is shown as verified: the gateway vouched for that very person. Event stream. Two fields on the TrustGate event, both documented in the Event schema: OpenTelemetry. Exported as trustgate.end_user plus trustgate.end_user.id, .email, .name, .role and .source. Values are trimmed and capped at 256 characters. A declaration that names nobody — only a role, say — records no identifier, so nothing groups under an empty user.

Not an identity

TrustGate distinguishes two different things, and so should any query you write over this data: Anyone who can reach your gateway with a valid API key can put any name in these headers. Use them to understand and bill traffic; do not use them to decide who may do what. If you need a person TrustGate verifies, send their own IdP token — see Verified attribution with SSO. For an end user whose identity TrustGate acts on in MCP, that is a different mechanism — see End users of your application for the MCP case, where each person connects their own upstream accounts.
  • Event schema: the field contract alerts and analytics match on
  • Applications: API keys, bindings, and the principal behind a request
  • Authentication: trusting your identity provider and admitting its tokens
  • Open WebUI: every way Open WebUI identifies its users, end to end