Provisioning your pk_
<VoiceBotWidget> needs a pk_ publishable key. The key is bound to your tenant and to an
allow-list of exact origins that may use it.
The publishable key
The browser widget authenticates with a publishable key — pk_…. It is a public, origin-locked
identifier, like a Stripe pk_: shipping it in your client bundle or page markup is safe and
intended. It is not the HMAC shared secret (the sk_-class, server-only secret — a base64
32-byte shared_secret_b64, not an sk_-prefixed token) used for server ingest; that secret never
touches the browser.
You give the widget your pk_; the deployed bundle self-mints its session. The flow is:
- The widget exchanges your
pk_plus the browser-setOriginfor a short-lived, origin-locked session token. The client never sends atenant_id. - The backend resolves the tenant from the
pk_and signstenant_idinto the token claim — so cross-tenant access is structurally impossible from the client side. - The WebSocket upgrade re-checks
Originagainst the token claim and closes (4403) on a mismatch.
The security boundary is the origin allow-list, not the secrecy of the key. A site that copies
your pk_ is denied at the mint gate because its Origin is not on your list.
If the current Origin is not allow-listed, the token mint returns 403 and the widget simply does
not mount — your page is never broken. If the widget doesn't appear, check the console for a
public-token exchange failed (403) and add the exact origin.
Getting a key
Keys are self-service in your VoiceBot cabinet: open Integrations, create (or copy) the publishable key for your store, and register the exact origins allowed to use it — including your local development origin. The same screen rotates and revokes keys and edits the allow-list.
A real key is pk_ followed by 32 url-safe characters — copy it exactly as issued (there is no
live/test segment). See API keys for which credential goes where.
Origin allow-list rules
Your pk_ is bound to an allow-list of exact origins that may use it. Matching is exact, with
no wildcards — the origin is the security boundary.
- List every origin explicitly.
https://shop.example,https://www.shop.example, andhttps://app.shop.exampleare three different origins. wwwand subdomains are not inherited — register each one separately.- An origin is
scheme://host[:port].http≠https, and a non-default dev port is part of the origin, so register your local origins too (e.g.http://localhost:5173). Origin: nullis never accepted — sandboxed iframes,file://, and other opaque origins cannot mint a token.
The origin is checked twice: once when minting the session token (HTTP 403 on miss) and again on
the WebSocket upgrade (close code 4403 on miss). Both must see an allow-listed origin.
Per environment
Register the origin for each environment the widget runs in, including local development. A Vite
dev server defaults to http://localhost:5173; a Nuxt dev server defaults to http://localhost:3000.
Both are distinct origins from production and must be listed explicitly if you want the widget to
mount there.
Content Security Policy
The wrapper injects a <script> from the CDN base (default https://api.monoverse.tech) and the
deployed bundle opens a WebSocket back to that host. If your site sends a CSP, allow them:
script-src 'self' https://api.monoverse.tech;
connect-src 'self' https://api.monoverse.tech wss://api.monoverse.tech;
If you override script-src (self-hosted API), substitute your host in both directives. The widget
renders into its own Shadow DOM host and does not need style-src exceptions for your page.
If the widget does not appear
It is almost always a missing origin: the token mint returns 403 and the widget silently does not
mount (your page is never broken). Check the browser console for public-token exchange failed (403) and add the exact current origin to the key's allow-list.
Example
A complete, copy-paste-correct Vue embed lives in
examples/embed/vue/ — both
the <VoiceBotWidget> form and the bare-embed component.