The Project ID becomes the JWT iss; the key signs it in-browser (ES256). The key is kept in this browser's localStorage only — never sent anywhere, never committed. Locally it falls back to /dev-private.pem, which the deployed build does not ship. Project ID is also settable as ?projectId=, and retrieval as ?retrievalMode=vector|pageindex — the switch the answer-quality suite flips to score one strategy against the other. Connect remounts the widget against these values; opening, closing and ending a conversation are the widget's own controls — the FAB, and the panel's minimize and End buttons. Every minted token's claims are logged to the console, since a 403 from the backend doesn't say whether it rejected the claims or the signature.
iss
localStorage
/dev-private.pem
?projectId=
?retrievalMode=vector|pageindex
403
Blank sends nothing, which is the default. This fixture isn't the Client's site, so an invented path is meaningless to the agent — knowledge chunks are keyed by the absolute URL they were crawled from, so a made-up one matches nothing and only biases retrieval with noise. To exercise page-anchored retrieval, paste a real page of the Project's site (https://…); to test the knowledge base on its own, leave it blank. Retrieval by query works either way.
https://…
Whatever you enter is forwarded as VoiceWidget('update', { pageContext: { url } }), the way a real integrator's SPA does on every navigation, and the loader resolves a relative path against this page. Nothing is rendered for it — the page context is a claim the host makes, not something derived from a page. Also settable at load time as ?pageUrl= (percent-encode a nested query).
VoiceWidget('update', { pageContext: { url } })
?pageUrl=