Skip to main content
Your app already knows the signed-in visitor’s account. Pass it to the guide as account facts and answers can use it: “You use 3 of your 5 seats”, “Your plan renews on 1 November”, “SSO is included in your plan”. Account facts evolve the existing init({ context }) channel; there is no second API. The rule behind them never changes:
Facts from the page ground answers. They never authorize anything. Puntego never derives authority from page text or from unsigned context.
Release preview: the @puntego/react helpers below (usePuntegoContext, signPuntegoAccountFacts) and the headless setContext are implemented and tested in the repository, but the packages are not yet published to npm. window.Puntego.init({ context }) works with the hosted script today.

Two trust levels

If a signed fact and a page-reported fact disagree, the signed fact wins and the page value is dropped. Facts written into page text, retrieved knowledge or the visitor’s message are not account facts. When a table, card or meter in an answer shows one of these facts, Puntego writes the value exactly as your app sent it, in the answer’s language, and records which fact it came from and at which trust level; the AI model only names the fact. See values from your data.

The contract (v1)

Every field is optional. Unknown fields are ignored; a bad value is dropped on its own and the rest still count. period is one of day, week, month, year or billing_period. accountId is only used to notice an account switch and to match a signed token; it is never shown to the AI model. traits reach the model only when you map them. Limits. Text values up to 120 characters; up to 12 usage meters, 40 entitlements and 24 traits; the whole accountFacts object up to 16 KB; a signed token up to 8 KB with a lifetime of at most 12 hours. Privacy. Account facts describe an account, never a person. Keys named like a credential or a direct identifier (email, phone, password, token, key, secret, session, card, address and similar) are always refused, whatever you configure. A value that contains an email address, phone number, card number, IBAN or national ID is dropped whole. Puntego reads account facts for each message and does not save them with the conversation or in workspace exports. The guide’s answers are kept like any other answer, so a fact an answer mentions, in its words or in a table or card shown with it, is part of that answer.

Pass page-reported facts

With the hosted script, hand the guide a function; it is read on every message:
A later init({ context }) replaces the function and settles an account switch at once; on its own it changes nothing else (the panel, theme and launcher stay as they are). With React, pass context to the provider, or add it where your app loads the account with usePuntegoContext:
The hook copies the value as plain JSON; the guide always reads the latest render. With the headless client, call client.setContext({ accountFacts }) whenever the facts change.

Sign facts on your server

  1. In the dashboard, open Settings → Account facts and choose Create signing secret (owners and admins; it asks for two-factor authentication). The secret starts with afs_ and is shown once.
  2. Store it in server-only configuration, for example PUNTEGO_ACCOUNT_FACTS_SECRET. Never ship it to the browser.
  3. Sign the facts from your authenticated session and hand the token to the page as signedAccountFacts:
A single-page app can refresh the token before it expires with createPuntegoAccountFactsRoute({ appId, secret, resolve }), a POST handler for Next.js App Router, Remix or any Fetch API runtime that answers { signed_account_facts, expires_at } with Cache-Control: private, no-store. Audience. appId must be the app id of the widget on that page: your production gp_… id, or the gp_test_… id in test mode. A token for another app is refused. Expiry. One hour by default, at most 12 hours; Puntego allows two minutes of clock skew. An expired token is ignored (the message is still answered, without the signed facts), so refresh it on your normal session cadence. Account. The token’s subject is accountId. If facts.accountId is set it must match. When the visitor is also signed in with a customer identity token, signed facts about any other account than the verified one are ignored. Otherwise, when the page’s accountFacts.accountId names another account than the token, the conversation is that account’s and the token’s signed facts are ignored for the message (for example, a token not yet refreshed after an account switch). Person. A role and a contact email belong to one person of the account, not to the account. Sign userId (the same externalId you pass to mintPuntegoIdentity) to bind them to that person. When the visitor is also signed in with a customer identity token, a token signed for another person of the account is ignored (for example, one not yet refreshed after someone else signed in), and a token without userId still grounds answers but its role never meets an audience rule and its contact email is not used. Other languages. The token is a compact JWS: header exactly {"alg":"HS256","kid":"afk_<id>","typ":"JWT"}, where <id> is the 20 characters after afs_ in your secret; claims { "aud": appId, "sub": accountId, "iat", "exp", "v": 1, "facts": { … } }, plus "user": userId when you sign the person and "contact": { "email": … } when you sign the visitor’s email (see below); signature HMAC-SHA256 over header.payload with the whole secret string as the key. Any other header field, algorithm or key id is refused, and so is a user that is not a non-empty id. The visitor’s own email. Governed actions that file something in the visitor’s name (a support ticket, a meeting) need their email. Sign it with contactEmail (one plain ASCII address; sign an internationalized domain in its xn-- form) and Puntego fills that input itself when the visitor confirms; it is never shown to the AI model, never an account fact and never part of a prompt. A customer identity token with an email trait does the same. Without either, those actions are not offered at all, so the guide never asks the visitor for an email it could not use:
Rotation. Rotate secret issues a new secret; the previous one keeps working for 24 hours so you can deploy without a gap. Turn off signed facts stops every secret at once.

From a host read tool

When the facts are expensive to compute on every page, let the guide ask for them only when a question needs them: a host read tool can return { signedAccountFacts } (a token your server signed) and the guide verifies it exactly like the one above. Anything else a tool returns grounds answers like page-reported facts.

Map your own keys

If your app already puts account data in init({ context }) under its own keys, map them in Settings → Account facts → Your keys instead of changing code. Use a dot for a nested key: billing.plan reads { billing: { plan } }. Keys match exactly, including case: A typed accountFacts value wins over a mapped key for the same fact. Mapped keys, like accountFacts and signedAccountFacts themselves, never also reach the model as generic page context. Signed only. Mark a mapped key, or a built-in fact such as Plan, as Signed only and the guide uses it only when it arrives in signedAccountFacts. Use it for anything that must not be taken from the browser’s word: discounts, contract terms, limits that change what you promise.

Account switches

When the account the facts describe changes (accountFacts.accountId, or the signed token’s subject, goes from account A to account B), the guide starts a new conversation straight away: the transcript with its tables and cards, any answer still streaming and the conversation’s server history belong to A and are not carried into B. A reload into B does not resume A’s conversation either. A page that reports no facts is not a switch; the guide keeps remembering the last account, so A → no facts → B still starts over. Signing out is handled by your identity lifecycle (destroy(), or the React provider’s customerIdentityToken). Always include accountId when a visitor can belong to more than one account. Returning-visitor memory is not part of the conversation and is not cleared by an account switch. It is the visitor’s own opt-in and is kept per person, not per account: per signed customer when you use customer identity, otherwise per browser. If people from different accounts share one browser, sign them in with customer identity so each keeps their own memory, or leave memory off.

Troubleshooting

  • The guide does not use a fact. Check the key is not refused (see Privacy), the value is within the limits, and, for a mapped key, that it is mapped. A fact marked Signed only needs signedAccountFacts.
  • Signed facts are ignored. The token is expired, was signed for another app id (test and production differ), names another account than the signed-in customer or than the page’s accountFacts.accountId, or its secret was rotated more than 24 hours ago or turned off.
  • accountFacts was not sent in the console. The object is not plain JSON or is over 16 KB; the message is still sent without it.