Skip to main content
Version: v1

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.

CredentialReal formatPublic?Lives inAuthorizesGet it from
Web publishable keypk_ + 32 url-safe charsYes — origin-lockedYour site's HTML / JS bundleWidget session minting on allow-listed origins; also pair-by-key to bootstrap a producerCabinet → Integrations
Mobile publishable keyvb_pk_ + 32 url-safe charsYes — bundle-id-lockedYour app binaryMobile session-token minting for allow-listed app_ids (Mode B)Cabinet → Integrations → Mobile app
Ingest shared secretbase64 of 32 random bytes (shared_secret_b64)No — server onlyYour backend, encrypted at restHMAC-signing of every ingest request; Mode A mobile token mintingReturned 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 by POST /pair to 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.

The cabinet shows the ingest secret as 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_ and vb_pk_ are identifiers, not secrets (like a Stripe pk_). The security boundary is the origin allow-list (web) or the app_id allowlist + 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?