Identity JWT for Generic stores
Mint a short-lived HS256 identity token on your server for signed-in customers and pass it to the widget, with Python, Node.js and PHP examples.
On a Generic store, your server tells ShopAssist who is signed in by minting a short-lived JWT. The widget asks your page for it when the shopper opens the chat.
Get the identity secret
In Settings → Connection → Signed-in customers, click Create identity secret. The secret is shown once, base64-encoded, with its key id (k_…). Store both in your server's secret store. The HMAC key is the decoded bytes of the secret, not the base64 text.
Token claims
| Claim | Value |
|---|---|
Header alg / typ / kid | HS256 / JWT / your key id |
iss | generic:sto_… (your store id) |
aud | shopassist |
sub | Your customer id (string, up to 128 characters) |
sid | The widget session id passed to your identify callback |
iat, exp | Now, and at most 10 minutes later (the examples use 5) |
jti | At least 128 random bits |
email, name (optional) | Shown to your team on handoff only; never used to find orders |
sid is the widget session id that the widget passes to the identify callback.
Server side
# Python (PyJWT)
import base64, secrets, time, jwt
def mint_shopassist_token(store_id: str, key_id: str, secret_b64: str, customer_id: str, session_id: str) -> str:
now = int(time.time())
claims = {"iss": f"generic:{store_id}", "aud": "shopassist", "sub": str(customer_id), "sid": session_id,
"iat": now, "exp": now + 300, "jti": secrets.token_hex(16)}
return jwt.encode(claims, base64.b64decode(secret_b64), algorithm="HS256", headers={"kid": key_id})// Node.js (jose)
import { SignJWT } from "jose"
import { randomBytes } from "node:crypto"
export async function mintShopAssistToken({ storeId, keyId, secretB64, customerId, sessionId }) {
return new SignJWT({ sid: sessionId })
.setProtectedHeader({ alg: "HS256", typ: "JWT", kid: keyId })
.setIssuer(`generic:${storeId}`)
.setAudience("shopassist")
.setSubject(String(customerId))
.setIssuedAt()
.setExpirationTime("5m")
.setJti(randomBytes(16).toString("hex"))
.sign(Buffer.from(secretB64, "base64"))
}<?php // PHP (firebase/php-jwt ^6)
use Firebase\JWT\JWT;
function mint_shopassist_token(string $storeId, string $keyId, string $secretB64, string $customerId, string $sessionId): string {
$now = time();
$claims = ['iss' => "generic:$storeId", 'aud' => 'shopassist', 'sub' => $customerId, 'sid' => $sessionId,
'iat' => $now, 'exp' => $now + 300, 'jti' => bin2hex(random_bytes(16))];
return JWT::encode($claims, base64_decode($secretB64), 'HS256', $keyId);
}Expose it as an endpoint on your site, for example POST /my-server/shopassist-token. The endpoint MUST be POST, send Cache-Control: no-store, authenticate the shopper with your own session, and return null for guests.
Browser side
ShopAssist.identify(async ({ sessionId }) => {
const r = await fetch("/my-server/shopassist-token", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ session_id: sessionId }),
})
return r.ok ? ((await r.json()).token ?? null) : null
})Call ShopAssist.identify(null) when the customer signs out. The provider is called with a timeout of 8 seconds; return null for guests.
Verification errors
If a token is rejected, the widget continues as a guest and the dashboard shows the reason (identity_invalid):
| Reason | Meaning |
|---|---|
malformed | The token is not a valid JWT. |
algorithm | The token is not signed with HS256. |
unknown_kid | The `kid` header does not match the store's current or previous identity key. |
signature | The signature does not verify with the identity secret. |
issuer | `iss` is not `generic:{store_id}` (or the platform issuer). |
audience | `aud` is not `shopassist`. |
session_mismatch | `sid` is not the widget session that requested the token. |
expired | The token has expired. |
not_yet_valid | `iat` or `nbf` lies in the future (check the server clock). |
lifetime_too_long | `exp − iat` is longer than 300 seconds. |
Never put the token in your page HTML or local storage, and never mint it in the browser: anyone with the secret can sign in as any customer. If the secret leaks, rotate it immediately.
Last updated on