Stripe Payments — Setup Guide
Back to Superadmin  ·  Agent Builder

💳 Stripe Payments in Agent Builder Flows

Collect real card payments inside your chatbot using Stripe Elements — PCI-compliant, zero card data on your server, supports Stripe Connect for split payments to connected accounts.

PCI-DSS Level 1 Stripe Elements Stripe Connect 4 Edge Functions
How It Works
User in Chat
Payment Node
Edge Function
(create_intent)
Stripe API
Stripe.js encrypts card
Payment succeeded
Next node in flow
Card data never touches your server. Stripe.js encrypts it in the browser and sends it directly to Stripe's servers. Your Edge Function only ever sees the client_secret and PaymentIntent ID.
1
Create / Configure Your Stripe Account
You need a Stripe account with API keys ready.
Create a Stripe account

Go to dashboard.stripe.com/register and sign up if you don't already have an account.

Get your API keys

In the Stripe Dashboard → Developers → API keys. Copy both the Publishable key (pk_live_…) and Secret key (sk_live_…). Use pk_test_ / sk_test_ keys for testing.

Enable required payment methods

Dashboard → Settings → Payment methods. Enable Cards (Visa, Mastercard, Amex). Optionally enable Apple Pay, Google Pay.

2
Run the Database Migration
Create the stripe_payments table in your Supabase project.
Run this SQL in your Supabase project → SQL Editor. This creates the table that stores webhook payment events.
-- Run in Supabase SQL Editor CREATE TABLE IF NOT EXISTS stripe_payments ( id TEXT PRIMARY KEY, -- Stripe event ID (evt_xxx) intent_id TEXT NOT NULL, -- pi_xxx PaymentIntent ID amount INTEGER, -- amount in cents currency TEXT, status TEXT, -- succeeded | failed | canceled description TEXT, metadata JSONB, account_id TEXT, -- connected Stripe account (acct_xxx) created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); -- Also add Stripe key columns to platform_settings (if not already present) ALTER TABLE platform_settings ADD COLUMN IF NOT EXISTS stripe_publishable_key TEXT, ADD COLUMN IF NOT EXISTS stripe_secret_key TEXT, ADD COLUMN IF NOT EXISTS stripe_webhook_secret TEXT, ADD COLUMN IF NOT EXISTS stripe_platform_fee_pct NUMERIC DEFAULT 0; -- RLS: allow service role to write, anon to read their own payments ALTER TABLE stripe_payments ENABLE ROW LEVEL SECURITY; CREATE POLICY "service_all" ON stripe_payments FOR ALL TO service_role USING (true); -- ══════════════════════════════════════════════════════════════ -- NEW: stripe_connect_accounts — company-level Stripe Connect -- One row per company (id = your internal sb_accounts.id). -- Populated by stripe-connect-onboard, kept in sync by stripe-webhook -- on account.updated, read by stripe-create-payment to auto-route -- payments to the company's own connected Stripe account. -- ══════════════════════════════════════════════════════════════ CREATE TABLE IF NOT EXISTS stripe_connect_accounts ( id TEXT PRIMARY KEY, -- = sb_accounts.id stripe_account_id TEXT NOT NULL, -- acct_xxx email TEXT, status TEXT DEFAULT 'pending', -- pending | pending_review | active charges_enabled BOOLEAN DEFAULT false, payouts_enabled BOOLEAN DEFAULT false, details_submitted BOOLEAN DEFAULT false, updated_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS stripe_connect_accounts_stripe_acct_idx ON stripe_connect_accounts (stripe_account_id); ALTER TABLE stripe_connect_accounts ENABLE ROW LEVEL SECURITY; CREATE POLICY "service_all_connect" ON stripe_connect_accounts FOR ALL TO service_role USING (true); CREATE POLICY "auth_read_connect" ON stripe_connect_accounts FOR SELECT TO authenticated USING (true);
After running the SQL, go to Table Editor in Supabase to confirm stripe_payments, stripe_connect_accounts both appear and platform_settings has the new columns.
3
Deploy the 4 Edge Functions
All 4 functions are already in supabase/functions/ — deploy them using the Supabase CLI.
POST
stripe-create-payment
Gets the publishable key and creates a PaymentIntent server-side. Called by sb-stripe.js from the browser. Auto-resolves a company's connected account via company_account_id.
POST
stripe-connect-onboard
Creates a Stripe Express connected account, returns the onboarding URL, and saves the account to stripe_connect_accounts when company_account_id is supplied.
GET
stripe-payment-status
Checks the status of a PaymentIntent by ID. Used for polling after payment confirmation.
POST
stripe-webhook
Receives Stripe webhook events, verifies signature, upserts payment records into stripe_payments, and syncs account.updated status into stripe_connect_accounts.

Deploy command

# Deploy all 4 Stripe Edge Functions at once supabase functions deploy stripe-create-payment supabase functions deploy stripe-connect-onboard supabase functions deploy stripe-payment-status supabase functions deploy stripe-webhook
The functions automatically read SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY from the Supabase environment — no manual secrets needed in the Supabase Dashboard → Secrets.
No CLI? Use stripe-deploy.html instead — copy-paste every SQL statement and every function's full source directly into the Supabase Dashboard (SQL Editor + Edge Functions Editor), no terminal required.
4
Save Your Stripe Keys in Platform Settings
The keys are stored server-side and read by Edge Functions — never exposed to the browser.
Open Superadmin → Platform Settings → Stripe Payments

Go to superadmin.html → click Platform Settings in the sidebar → scroll to the Stripe Payments card.

Paste all 3 keys

Publishable key (pk_live_…), Secret key (sk_live_…), Webhook signing secret (whsec_…). Click Save Stripe Settings.

5
Configure the Stripe Webhook
This lets Stripe notify your app when payments succeed or fail — even if the user closes the browser tab.
Add a webhook endpoint in Stripe Dashboard

Stripe Dashboard → Developers → Webhooks → Add endpoint.
Endpoint URL: https://YOUR_PROJECT.supabase.co/functions/v1/stripe-webhook

Select these events to listen to

payment_intent.succeeded payment_intent.payment_failed payment_intent.canceled account.updated

Copy the Signing Secret

After creating the endpoint, click it → Signing secret → Reveal → copy whsec_…. Paste into Platform Settings → Stripe → Webhook Signing Secret.

The webhook verifies the Stripe-Signature header using HMAC-SHA256. If stripe_webhook_secret is empty, the signature check is skipped (useful for testing — add it before going live).
6
Build a Flow with a Payment Node
Drag a Payment node from the node picker and configure it in the inspector.
Payment Node Inspector Fields
Field Example Notes
Amount29.99In dollars (stored as cents internally)
Currencyusd3-letter ISO code
DescriptionSupport Plan — MonthlyShown on Stripe dashboard + receipt
Pay Button LabelPay $29.99Leave blank to auto-generate
Status variablepaymentStatusSet to succeeded or failed
Intent ID variablepaymentIntentIdStripe pi_xxx ID
Connected Accountacct_1ABC…Manual override. Leave blank to auto-use the company's own connected account (if onboarded) or the platform account
On Success → go toConfirmation nodeNext node after payment succeeds
On Failure → go toRetry / Help nodeNext node if card is declined

What the user sees in the chat

Support Plan — Monthly USD 29.99
4242 4242 4242 4242    12/27    123
Secured by Stripe
Stripe Connect (Split Payments)
Charge customers and automatically split the payment with a connected business account.
How Stripe Connect works in your flows
  1. Your platform collects the card payment
  2. Stripe automatically deducts your platform fee % (set in Platform Settings)
  3. The remainder is transferred to the connected account (acct_xxx)

To onboard a business, call the stripe-connect-onboard Edge Function from your app — pass company_account_id so the resulting account is automatically saved and later auto-used by Payment nodes:

// Example: onboard a company's own Stripe account const res = await fetch('https://YOUR.supabase.co/functions/v1/stripe-connect-onboard', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ANON_KEY' }, body: JSON.stringify({ email: 'partner@company.com', return_url: 'https://yourapp.com/onboard-complete', refresh_url: 'https://yourapp.com/onboard-refresh', company_account_id: '<their sb_accounts.id>', // auto-saves to stripe_connect_accounts metadata: { company_id: 'acme-corp' } }) }); const { url, account_id } = await res.json(); // account_id is already saved to stripe_connect_accounts — just redirect: window.location.href = url;
Auto-routing payments to the company's account

Once onboarding completes (Stripe fires account.updated → webhook flips charges_enabled to true), just pass company_account_id instead of a manual account_id when creating a PaymentIntent — stripe-create-payment looks it up automatically:

// sb-stripe.js — mountPaymentCard() opts { amount_cents: 2999, currency: 'usd', description: 'Support Plan', company_account_id: '<their sb_accounts.id>' // no manual acct_xxx needed }
Still to build: a company-facing stripe-settings.html page (in App Settings, alongside SMS/Email Settings) with a "Connect with Stripe" button that calls the flow above. The Edge Functions + stripe_connect_accounts table are ready — this UI page is the remaining piece. See stripe-deploy.html → Company-Level Stripe Connect for the full architecture.
Test Cards
Use these card numbers in test mode — any future date for expiry, any 3 digits for CVC.
Card NumberResultUse case
4242 4242 4242 4242✅ SucceededHappy path — payment succeeds
4000 0000 0000 0002❌ DeclinedTest your "On Failure" routing
4000 0025 0000 31553D SecureAuthentication required
4000 0000 0000 9995❌ Insufficient fundsTest decline reasons
Payments Dashboard
Live view of all payments recorded by the webhook. Loads from the stripe_payments table.
Date Description Amount Status Intent ID
Loading payments…
Troubleshooting
❌ "Could not fetch Stripe publishable key"
  • Check that stripe_publishable_key is not empty in platform_settings (row id=global).
  • Make sure the stripe-create-payment Edge Function is deployed and responding.
  • Test with: curl -X POST https://YOUR.supabase.co/functions/v1/stripe-create-payment -H "Authorization: Bearer ANON_KEY" -d '{"action":"get_publishable_key"}'
❌ Card declined in test mode
  • Make sure you're using sk_test_ / pk_test_ keys (not live keys) during testing.
  • Use the test card 4242 4242 4242 4242 with any future date and any CVC.
❌ Webhook events not appearing in stripe_payments
  • Check the Stripe Dashboard → Webhooks → your endpoint → Event deliveries for failed attempts.
  • Confirm the stripe_webhook_secret in platform_settings matches the Signing secret shown in Stripe Dashboard.
  • The stripe_payments table must exist with RLS allowing service_role to write (see Step 2 SQL).
❌ CORS error calling Edge Function
  • All 4 Edge Functions already include Access-Control-Allow-Origin: * headers.
  • Make sure you're sending the Authorization: Bearer ANON_KEY header — missing auth causes Supabase to return a 401 which browsers treat as a CORS failure.