Skip to main content
Zap uses Supabase project wzrdstudio for creator wallet authentication and bring-your-own-key (BYOK) provider secret storage. All creator API keys are encrypted server-side before being written to the database and are revealed in plaintext only during authenticated live runs — never returned to the browser. The /settings page lets creators connect their wallet and manage BYOK keys; the /api/secrets route proxies all secret operations through the zap-user-secrets edge function.

Environment Variables

The web app requires three Supabase variables. Set them in Vercel and in your local .env.local:
NEXT_PUBLIC_SUPABASE_URL=https://<project-ref>.supabase.co
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=<publishable key>
# or, for legacy projects:
NEXT_PUBLIC_SUPABASE_ANON_KEY=<anon key>
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY is the current Supabase naming convention. NEXT_PUBLIC_SUPABASE_ANON_KEY is the equivalent legacy alias — set whichever matches your Supabase dashboard.

Database Migration

Apply the migration at supabase/migrations/20260703000000_zap_user_secrets.sql before enabling live BYOK runs.
supabase db push --project-ref <your-project-ref>
The migration creates or extends the user_secrets table with the full encrypted storage shape:
create table if not exists public.user_secrets (
  user_id uuid not null references auth.users(id) on delete cascade
);

alter table if exists public.user_secrets
  add column if not exists user_id     uuid references auth.users(id) on delete cascade,
  add column if not exists secret_type text,
  add column if not exists ciphertext  text,
  add column if not exists last4       text,
  add column if not exists nonce       text,
  add column if not exists provider    text,
  add column if not exists key_version integer     not null default 1,
  add column if not exists created_at  timestamptz not null default now(),
  add column if not exists updated_at  timestamptz not null default now();
It also creates wallet_auth_users and wallet_auth_nonces tables, unique indexes, and a full set of Row Level Security policies so that each user can only access their own secrets.
The existing user_secrets table (if present from an earlier deployment) and the legacy manage-user-secrets edge function are not aligned with the encrypted shape above. Apply this migration and redeploy the zap-user-secrets edge function before any live BYOK runs depend on Supabase, or encrypted key retrieval will fail.

Row Level Security

RLS is enabled and forced on user_secrets, wallet_auth_users, and wallet_auth_nonces. The policies enforce:
  • SELECT, INSERT, UPDATE, DELETE on user_secrets are scoped to auth.uid() = user_id
  • SELECT on wallet_auth_users is scoped to auth.uid() = user_id
  • No direct browser access to wallet_auth_nonces — nonce records are written and consumed only by edge functions
Only JWT-protected edge functions return masked secret metadata to the browser. Server-side runtime paths may retrieve plaintext keys only for the authenticated owner of the active run, using the x-zap-server-secret header alongside the user JWT.

Edge Functions

Deploy both functions with supabase functions deploy <name>.

zap-user-secrets

Handles list, save, and delete of encrypted BYOK secrets. Returns masked metadata (e.g. last4) to the browser; returns plaintext only to the Zap server. Required Supabase secrets:
USER_SECRETS_ENCRYPTION_KEY=<long random value>
ZAP_SECRET_REVEAL_TOKEN=<long random value must match Vercel>
Access model:
CallerAllowed operations
Browser (user JWT)List masked secrets, save a new key, delete a key
Zap server (user JWT + x-zap-server-secret)All of the above, plus plaintext key reveal for a live run
The ZAP_SECRET_REVEAL_TOKEN must match the value set in Vercel’s environment variables. This shared secret is what restricts plaintext reveals to the Zap server only.

zap-wallet-proof

Handles EIP-191 wallet signature verification and Supabase Auth session minting for wallet-based login. Required Supabase secrets:
ZAP_WALLET_AUTH_SECRET=<long random value>
ZAP_WALLET_TOKEN_TTL_SECONDS=604800
Flow:
  1. Client submits a signed EIP-191 payload and a one-time nonce to /api/auth/wallet-proof
  2. The Next.js route proxies the request to zap-wallet-proof (or the function named in ZAP_WALLET_PROOF_FUNCTION)
  3. The function verifies the wallet signature, confirms the nonce has not been used, and records it in wallet_auth_nonces
  4. It creates or reuses a Supabase Auth user mapped in wallet_auth_users (keyed by wallet address)
  5. Supabase Auth mints a session token, which is returned to the client
ZAP_WALLET_TOKEN_TTL_SECONDS controls session lifetime; 604800 equals seven days.
SUPABASE_URL and SUPABASE_SECRET_KEYS are injected automatically by the Supabase Edge Function runtime — do not set them manually. The legacy SUPABASE_SERVICE_ROLE_KEY is also supported if your project uses it.

BYOK Secret Types

The following secret_type values are supported by the zap-user-secrets edge function. Each maps to a provider credential a creator can supply via /settings.
secret_typeDescription
gmi_api_keyGMI (Generative Media Intelligence) API key
gmi_org_idGMI organisation ID
fal_keyFal.ai API key
runware_keyRunware API key
prodia_keyProdia API key
openrouter_keyOpenRouter API key
ai_gateway_api_keyAI gateway API key
Do not use the legacy profiles.*_api_key columns for Zap provider execution. All provider credentials for live runs must go through the encrypted user_secrets table and the zap-user-secrets edge function.