Signed integration tokens
An integration token tells Gauge who your user is and which Integration of your App they are on (for example customer or salon). Your server mints it; the widget, WebView or portal passes it on.…
An integration token tells Gauge who your user is and which Integration of your App they are
on (for example customer or salon). Your server mints it; the widget, WebView or portal passes it
on. Gauge verifies it on every session open.
Use it instead of the older identityHash for anything new. identityHash keeps working, but only on
your App's default integration, and it never unlocks actions that read a customer's data (order
lookups, payouts). A token does.
Mint one (your server)
Node (npm install @gauge/support):
import { mintIntegrationToken } from '@gauge/support';
const token = await mintIntegrationToken(process.env.GAUGE_IDENTITY_SECRET, {
appId: process.env.GAUGE_APP_ID, // Console → Apps → your App → Install
userId: 'u_1029', // YOUR user id
integration: 'customer', // the Integration key
attrs: { plan: 'pro' }, // optional, verified attributes
});Python (pip install gauge-support):
import os
from gauge_support import mint_integration_token
token = mint_integration_token(
os.environ["GAUGE_IDENTITY_SECRET"],
app_id=os.environ["GAUGE_APP_ID"],
user_id="u_1029",
integration="customer",
attrs={"plan": "pro"},
)Never send the secret to a browser or ship it in an app. Mint a new token for every session open.
What is inside
A compact JWT: header {"alg":"HS256","typ":"JWT"}, HMAC-SHA256 with a token key derived from your
App's identity secret: tokenKey = hex(HMAC-SHA256(key = "gauge.integration-token.v1", message = secret))
(lowercase hex, used as the HS256 secret). The SDK helpers do this for you; with another JWT library, pass
integrationTokenKey(secret) / integration_token_key(secret) as its secret. Never sign with the raw
identity secret: a token signed that way is refused (bad_signature), because an identityHash your
server hands out is an HMAC under that same secret.
| Claim | Meaning |
|---|---|
iss | Your App id |
sub | Your user id. Gauge keys the conversation and the customer record on it |
key | The Integration key (^[a-z][a-z0-9_]{0,31}$) |
attrs | Optional. Up to 16 flat values (string ≤ 200, number, boolean). Assistant rules and SLA tiers read them, and only from a verified token |
name, email | Optional, shown to your team |
iat, exp | Epoch seconds. exp - iat ≤ 600 |
jti | Single-use nonce (≤ 128 chars). The SDK helpers set a random one for every token. See Replay |
Rules Gauge applies, in this order: alg must be HS256 · signature (current secret, or the previous
one while a rotation overlap is open) · no unknown claims · life ≤ 600 s · not expired (60 s grace) ·
iat not in the future (60 s grace) · iss is this App · key is the integration being opened ·
the user is granted that integration · jti not used before.
Replay: one token, one session open
A token is a bearer credential for up to 10 minutes. So that a copied token (a log line, a proxy, a
browser extension) can't open a verified chat as your user and reach their orders or payouts, a token
that carries a jti is single-use: the first session open consumes it, and any later open with the
same token (from any browser) is refused with token_replayed. Gauge only consumes a jti after the
signature checks out, so a forged token can't burn one of yours.
mintIntegrationToken(JS) andmint_integration_token(Python) always add a randomjti(pass your own withjtiif you need to correlate). The console's Test as … tokens carry one too.- In the widget, use
getToken(it fetches a fresh token on every open and re-open). A statictokenworks once: the widget's later re-reads with the same token are refused and it keeps the session it already has, but a page that re-initialises with the same token will not reopen. - Backward compatibility during P1: a token without
jti(hand-signed with another JWT library) is still accepted and is not replay-checked. Add a randomjtinow; a later release will require it.
Why a token was refused
The error message starts with the code. The console's Token checker (Developers → Support) decodes a pasted token and tells you which rule failed. It never shows the secret or the user's data.
| Code | HTTP | Fix |
|---|---|---|
malformed | 401 | Not a JWT, or a claim Gauge doesn't know. Use the SDK helper |
bad_alg | 401 | Header must be {"alg":"HS256","typ":"JWT"} |
bad_signature | 401 | Wrong secret, or the token was edited. Re-sign after rotating |
expired | 401 | Mint a new token per session open |
not_yet_valid | 401 | Your server clock is ahead. Sync it (NTP) |
too_long | 401 | Keep exp - iat at 600 s or less |
wrong_app | 401 | iss is another App's id |
wrong_key | 401 | The token names a different integration than the one being opened |
revoked | 401 | The user's grants changed after the token was minted. Mint again |
token_replayed | 401 | This token (its jti) already opened a session. Mint a fresh token per open (getToken) |
unknown_integration | 404 | No integration with that key on the App |
integration_not_granted | 403 | The user isn't granted that integration. Add it with customer sync |
sandbox_only | 401 | A console test token was used on a live integration |
Grants
On an App with more than one integration, a user may open only the integrations listed for them:
PUT /v1/support/customers/by-ref/u_1029
{ "email": "layla@example.com", "projectIds": ["<appId>"], "integrationKeys": { "<appId>": ["customer"] } }An empty list means the default integration only. Removing a key ends the user's live chats on it
at their next message, and refuses tokens minted before the change. A user Gauge has never seen
(no customer record) is bound by the key your server signed.
Rotating the secret
Generate a new secret in the console. The old one keeps verifying during the overlap window, so roll your servers onto the new one inside it.
Testing
verifyIntegrationToken (JS) and verify_integration_token (Python) apply the same rules and return
the same codes, which is handy in your own tests. Both SDKs pass the shared vectors in
packages/sdk-testvectors/integration-token.json, which the Gauge server generates. In the console,
Test as <integration> mints a token for draft and sandbox integrations only.
Quick start: Go (`net/http`)
Go ≥ 1.21, standard library only. About 15 minutes to a verified chat message.
Keeping your users in sync with Gauge Support
This is the integrator's guide to the customer sync API. It's written for a SaaS backend (Salonvira is the first one) that owns its users and wants Gauge Support to mirror them: who exists, what…