Vault Configuration for Self-Hosting
Switchbord stores per-workspace secrets in Supabase Vault — a Postgres extension that encrypts secrets at the database level. This page covers what self-hosters need to know to get vault working correctly, especially when running multiple workspaces.Overview: Two Modes of Secret Resolution
Switchbord supports two approaches to supplying runtime secrets like the Meta access token: Vault mode (default, recommended) Secrets are stored per-workspace invault.secrets, linked to each workspace via app_private.workspace_secret_bindings. The worker reads them at runtime using Supabase RPC functions. This is the correct approach for any deployment with more than one workspace.
Env var fallback (single-workspace only)
If SELF_HOSTED=true is set and a vault read fails, the worker falls back to WHATSAPP_META_ACCESS_TOKEN from the environment. This is a convenience for operators running a single workspace who want to skip vault setup entirely.
Why Multi-Tenant Deployments Must Use Vault
When the worker processes an outbound message, it resolves the Meta access token by looking up the workspace that owns the conversation. In vault mode, it callssb_read_workspace_secret(workspace_id, 'meta-access-token') — a scoped lookup that returns only the secret belonging to that workspace.
With SELF_HOSTED=true, the lookup is skipped entirely and the environment variable value is returned regardless of which workspace is making the request. There is no per-workspace isolation. A second workspace added later will silently use the first workspace’s token, which will fail or — worse — succeed with the wrong account.
If you initially deployed with
SELF_HOSTED=true for a single workspace and are now adding a second workspace, you must remove that flag, configure vault secrets for both workspaces, and restart the worker before the second workspace will function correctly.How Secrets Flow Through the System
1
Operator saves a credential in Settings
The operator opens Settings → Provider and enters a Meta access token (or API key for an AI provider). The Switchbord web app sends this to
/api/settings/secrets.2
API stores the secret in Supabase Vault
The API route calls
storeWorkspaceSecret(workspaceId, secretKind, value), which calls vault.create_secret() and inserts a row into app_private.workspace_secret_bindings mapping the workspace ID and secret kind to the vault secret ID.3
Worker reads the secret at send time
When the worker is about to send a message, it calls the Supabase RPC function
sb_read_workspace_secret with the workspace ID and secret kind. The function runs with SECURITY DEFINER privileges, reads from vault.secrets, and returns the decrypted value. No direct database connection is required from the worker.Supabase Setup Requirements
Vault Extension
Supabase Vault is enabled by default on all Supabase Cloud projects. If you are running a self-hosted Supabase instance, confirm the extension is enabled:Switchbord migrations also create the
app_private schema and the workspace_secret_bindings table automatically when you run supabase db push or apply migrations. You do not need to create these manually.RPC Functions
The worker uses two public RPC wrapper functions that must be present in your database:sb_read_workspace_secret(p_workspace_id uuid, p_kind text)— returns the decrypted secret value for a workspace and secret kindsb_resolve_workspace_by_verify_token(p_verify_token text)— returns the workspace ID matching a given Meta verify token
function does not exist errors, re-run your migrations against the target database:
Storing Secrets
There are two ways to populate vault secrets for a workspace: Settings UI (recommended) Open Settings → Provider in the Switchbord web app and enter credentials directly. The UI calls the vault write API automatically. No direct database access is needed. Supabase Dashboard SQL editor For initial setup or bulk loading, you can insert secrets directly:Worker Environment Variables
The worker needs only a Supabase service role key to read from vault. No direct database connection is required for reads. Required:
Optional:
Secret Kinds Reference
Troubleshooting
vault_unavailable
The vault extension is not enabled or the app_private schema is missing.
- Confirm the vault extension exists (see the SQL check above)
- Re-run Switchbord migrations:
supabase db push - Confirm the worker’s
SUPABASE_SECRET_KEYis a valid service role key, not the anon key
vault_read_error
The RPC function exists but returned an error when attempting to read a secret.
- Check that
sb_read_workspace_secretwas created successfully by your migrations - Confirm
SUPABASE_SECRET_KEYhas service role permissions (the anon key cannot callSECURITY DEFINERfunctions that check forservice_role) - In the Supabase SQL editor, verify the function exists:
\df sb_read_workspace_secret
vault_secret_missing
The function ran successfully but found no binding for the requested workspace and secret kind. The secret was never stored.
- Open Settings → Provider for the affected workspace and save the missing credential
- Or insert the binding manually using the SQL above
- Verify the workspace ID in
app_private.workspace_secret_bindingsmatches the workspace making the request
Worker sends messages with the wrong account
This is a symptom ofSELF_HOSTED=true being set in a multi-workspace deployment. The env var fallback credential is being used instead of per-workspace vault secrets. Remove SELF_HOSTED=true from the worker environment and ensure all workspaces have their secrets configured in vault.
Related Guides
- Vault Architecture — internals, schema design, and encryption details
- Secrets and Encryption — security policy and access controls
- Meta Credential Setup — how to obtain the credentials you will store in vault
- Webhooks — Meta webhook verification token setup