API keys — which key goes where
VoiceBot uses three credentials, one per surface. Two are public identifiers (safe to ship in a page or an app binary); one is a server-only secret. This page is the single map.
| Credential | Real format | Public? | Lives in | Authorizes | Get it from |
|---|---|---|---|---|---|
| Web publishable key | pk_ + 32 url-safe chars | Yes — origin-locked | Your site's HTML / JS bundle | Widget session minting on allow-listed origins; also pair-by-key to bootstrap a producer | Cabinet → Integrations |
| Mobile publishable key | vb_pk_ + 32 url-safe chars | Yes — bundle-id-locked | Your app binary | Mobile session-token minting for allow-listed app_ids (Mode B) | Cabinet → Integrations → Mobile app |
| Ingest shared secret | base64 of 32 random bytes (shared_secret_b64) | No — server only | Your backend, encrypted at rest | HMAC-signing of every ingest request; Mode A mobile token minting | Returned once by POST /pair or pair-by-key |
Two more strings you will meet in the flow — neither is a long-lived credential you store:
- Pair code
VB-XXXX-XXXX— a one-time code from the cabinet, consumed byPOST /pairto mint the shared secret. Dead after one use. - Session token — a short-lived JWT the widget and SDKs mint for themselves from a publishable key (or that your server mints in Mode A). You never copy or store it.
The real key formats
A real web key looks like pk_4T8mQzXcV1nY7wKfB2rL9pDhJ6sGaU0e — the prefix pk_ followed by 32
url-safe random characters. There is no live / test segment: if you see pk_live_…
anywhere, that is an old placeholder, not a different key type. Copy the key from the cabinet exactly
as issued. The mobile key is the same shape behind vb_pk_.
The docs use pk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx / vb_pk_… as placeholders — always substitute
your own key.
sk_…The cabinet references your ingest secret by a display id starting with sk_ so it can be
identified without being revealed. The actual credential your producer signs with is the base64
shared_secret_b64 value returned once at pairing — the sk_… display id is not the secret and
cannot sign anything.
Placement rules
pk_andvb_pk_are identifiers, not secrets (like a Stripepk_). The security boundary is the origin allow-list (web) or theapp_idallowlist + connection status (mobile) — never the secrecy of the key. Details: Tenant isolation and Auth & API keys (mobile).- The ingest shared secret must never reach a browser or an app binary. All first-party producer SDKs store it encrypted at rest (AES-256-GCM) and redact it from logs. If it leaks, unpair and re-pair to rotate.
- One tenant, all surfaces. All three credentials resolve to the same tenant server-side; the
client never sends a
tenant_id. Cross-tenant access is structurally impossible from a client.
Which one do I need right now?
- Putting the widget on a site →
pk_(Frontend — add the widget) - Syncing a catalog from your backend → pair code or
pk_, then the shared secret (Backend — get your catalog in) - Shipping a mobile app without a backend →
vb_pk_(Quickstart — mobile) - Shipping a mobile app with a backend → the shared secret, server-side (Mode A in Auth & API keys)