Skip to main content
Version: next

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 keypk_…. 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:

  1. The widget exchanges your pk_ plus the browser-set Origin for a short-lived, origin-locked session token. The client never sends a tenant_id.
  2. The backend resolves the tenant from the pk_ and signs tenant_id into the token claim — so cross-tenant access is structurally impossible from the client side.
  3. The WebSocket upgrade re-checks Origin against 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.

Failure is silent and safe

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, and https://app.shop.example are three different origins.
  • www and subdomains are not inherited — register each one separately.
  • An origin is scheme://host[:port]. httphttps, and a non-default dev port is part of the origin, so register your local origins too (e.g. http://localhost:5173).
  • Origin: null is never accepted — sandboxed iframes, file://, and other opaque origins cannot mint a token.
Two gates, same origin

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; Create React App to http://localhost:3000. Each is a distinct origin 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 scriptSrc (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.