Connect FlutterFlow to Stripe Connect by building a Firebase or Supabase proxy that holds your platform secret key, then wiring FlutterFlow API Calls to that proxy. The proxy creates Express accounts, generates Account Link onboarding URLs, and splits payments using application_fee_amount plus transfer_data[destination] — the pattern that powers marketplace payouts in mobile apps.
| Fact | Value |
|---|---|
| Tool | Stripe Connect |
| Category | Payments |
| Method | FlutterFlow API Call |
| Difficulty | Advanced |
| Time required | 3-4 hours |
| Last updated | July 2026 |
Why FlutterFlow's built-in Stripe is not enough for marketplaces
FlutterFlow ships a native Stripe Payments toggle in Settings & Integrations → Payments. It wires up a single-merchant checkout with almost no code — great for a straightforward product sale. But if your app connects buyers with multiple sellers (gig marketplaces, creator platforms, service directories), you need Stripe Connect. Connect lets your platform account receive a charge, take an application fee, and route the remainder to a connected seller's Stripe account automatically. That's a fundamentally different flow from what the native toggle provides.
The core security reality: your platform secret key (sk_*) is what authorises every Connect action — creating seller accounts, generating onboarding links, controlling money movement. Because FlutterFlow compiles to a Flutter app running on the user's device, any secret placed in an API Call header ships inside the app bundle and can be extracted. The only safe pattern is to put the sk_* key inside a Firebase Cloud Function or Supabase Edge Function, then call that proxy from FlutterFlow. Your app never touches the key directly.
Stripe Connect pricing layers on top of standard Stripe fees (2.9% + 30¢/charge in the US). Connect-specific fees — account maintenance and payout fees — vary by plan and region; check Stripe's current pricing page for the numbers that apply to your geography. The connect.stripe.com onboarding domain handles the seller KYC flow, and once a seller completes it, their account becomes available as a transfer destination.
Integration method
FlutterFlow API Calls route to a Firebase Cloud Function or Supabase Edge Function that holds your Stripe platform secret key. The proxy handles account creation, Account Link generation, and PaymentIntent creation with marketplace fee splits. The publishable key (pk_*) is used client-side in FlutterFlow for the Payment Sheet; the secret key (sk_*) never leaves the server.
Prerequisites
- A Stripe platform account with Connect enabled (stripe.com/connect → Apply for Connect in the dashboard)
- A Firebase project (Blaze plan for Cloud Functions) or a Supabase project (for Edge Functions)
- A FlutterFlow project using Supabase or Firebase as its backend for storing account status
- Your Stripe publishable key (pk_live_* or pk_test_*) and secret key (sk_live_* or sk_test_*) — keep the secret key out of FlutterFlow
- Basic familiarity with deploying a Firebase Cloud Function or Supabase Edge Function
Step-by-step guide
Enable Stripe Connect and collect your keys
Log in to your Stripe Dashboard at dashboard.stripe.com and navigate to Connect → Get started. Choose the Express account type — it handles KYC and identity verification on Stripe's side, which means you don't need to build that yourself. Once enabled, go to Developers → API keys. You'll see your publishable key (pk_test_* or pk_live_*) and your secret key (sk_test_* or sk_live_*). Copy both somewhere safe. The publishable key can be placed in FlutterFlow App State or App Values — it's safe for client use. The secret key must go into your backend environment: a Firebase Functions config value, a Supabase project secret, or an environment variable in your cloud functions deployment. Never paste sk_* anywhere inside FlutterFlow itself. Also note your platform's Stripe account ID (acct_*) from the Dashboard top-right — you may need it for some Connect API calls.
Pro tip: Create a second, restricted API key in the Stripe Dashboard (Developers → API keys → Create restricted key) that only has permissions for Connect, accounts, and payment intents. This limits blast radius if the proxy is ever compromised.
Expected result: You have your Stripe publishable key saved for later use in FlutterFlow, and your secret key stored securely outside the app.
Deploy a backend proxy for account creation and PaymentIntents
Create a Firebase Cloud Function (Node.js) or Supabase Edge Function (Deno) that handles three endpoints your FlutterFlow app will call: (1) POST /create-account — receives a seller's email and name, calls POST https://api.stripe.com/v1/accounts with account_type=express and the seller's details, then calls POST https://api.stripe.com/v1/account_links with the account ID plus a return_url and refresh_url that deep-link back into your app (use your app's universal link scheme, e.g. myapp://onboarding/complete). Return the account_links.url to the app so it can open the onboarding WebView. (2) POST /create-payment-intent — receives amount (in cents), currency, connected_account_id, and application_fee_amount; calls POST https://api.stripe.com/v1/payment_intents with transfer_data[destination] set to the seller's account ID. Return the client_secret to the app. (3) GET /account-status?account_id=acct_xxx — calls GET https://api.stripe.com/v1/accounts/{id} and returns payouts_enabled and charges_enabled so the app can show onboarding completion state. All three functions authenticate to Stripe using your sk_* from environment config. Store the proxy URLs (not the Stripe key) in FlutterFlow App Values.
1// Firebase Cloud Function example (Node.js)2const functions = require('firebase-functions');3const stripe = require('stripe')(functions.config().stripe.secret_key);4const express = require('express');5const cors = require('cors')({ origin: true });6const app = express();7app.use(cors);8app.use(express.json());910// 1. Create Express account + Account Link11app.post('/create-account', async (req, res) => {12 const { email, name } = req.body;13 const account = await stripe.accounts.create({14 type: 'express',15 email,16 capabilities: { card_payments: { requested: true }, transfers: { requested: true } },17 });18 const accountLink = await stripe.accountLinks.create({19 account: account.id,20 refresh_url: 'https://yourproxy.com/reauth',21 return_url: 'myapp://onboarding/complete',22 type: 'account_onboarding',23 });24 res.json({ account_id: account.id, onboarding_url: accountLink.url });25});2627// 2. Create PaymentIntent with marketplace split28app.post('/create-payment-intent', async (req, res) => {29 const { amount, currency, destination_account, application_fee_amount } = req.body;30 const paymentIntent = await stripe.paymentIntents.create({31 amount, // in cents, e.g. 5000 = $50.0032 currency,33 application_fee_amount, // e.g. 500 = $5.00 (10% fee)34 transfer_data: { destination: destination_account },35 });36 res.json({ client_secret: paymentIntent.client_secret });37});3839exports.stripeConnect = functions.https.onRequest(app);Pro tip: If you're using Supabase Edge Functions (Deno), replace the Node.js require() calls with import statements and use Deno.env.get('STRIPE_SECRET_KEY') to read the secret. Deploy with supabase functions deploy stripe-connect and set the secret via supabase secrets set STRIPE_SECRET_KEY=sk_live_xxx.
Expected result: Your proxy is deployed and reachable at a public HTTPS URL. You can test it with a tool like Postman: POST to /create-account with a test email returns a JSON with onboarding_url.
Configure FlutterFlow API Groups to call your proxy
In FlutterFlow, click API Calls in the left navigation panel. Click + Add and choose Create API Group. Name it StripeConnectProxy. Set the Base URL to your deployed proxy URL (e.g. https://us-central1-yourproject.cloudfunctions.net/stripeConnect or your Supabase Edge Function URL). You do not need any shared headers here — authentication happens inside the proxy, not from FlutterFlow. Now add individual API Calls within this group. First: click + Add Call → name it CreateAccount → set the Method to POST and the Path to /create-account. Switch to the Variables tab and add two Body variables: email (String) and name (String). In the Body tab, configure it as JSON with body { "email": "{{ email }}", "name": "{{ name }}" }. Click Response & Test, paste a sample response like { "account_id": "acct_xxx", "onboarding_url": "https://connect.stripe.com/express/onboarding/..." }, and click Generate JSON Paths. FlutterFlow will auto-generate $.account_id and $.onboarding_url paths you can bind to variables. Repeat this for CreatePaymentIntent (POST /create-payment-intent with amount, currency, destination_account, application_fee_amount body variables) and GetAccountStatus (GET /account-status with account_id as a query variable). Save all three API Calls.
1// Sample JSON response for CreateAccount — paste in Response & Test2{3 "account_id": "acct_1ExampleXXXXXXXX",4 "onboarding_url": "https://connect.stripe.com/express/onboarding/o.abc123"5}67// Sample JSON response for CreatePaymentIntent8{9 "client_secret": "pi_3ExampleXXXXXXXX_secret_XXXXXX"10}1112// Sample JSON response for GetAccountStatus13{14 "charges_enabled": true,15 "payouts_enabled": true16}Pro tip: Store the onboarding_url returned by CreateAccount in an App State variable (String, persisted: false) so you can pass it into the WebView widget in the next step.
Expected result: Three API Calls appear under StripeConnectProxy in the API Calls panel. Each shows green JSON path detections in the Response & Test tab.
Open the seller onboarding URL in an in-app WebView
When a seller taps 'Connect your bank account' in the app, you need to open the Stripe Express onboarding URL returned by CreateAccount. In FlutterFlow, you have two solid options. Option A (simpler): use a url_launcher Custom Action. Go to Custom Code → + Add → Action, name it LaunchUrl, add the pub.dev package url_launcher (version: ^6.2.6) in the Dependencies field, then paste a short Dart snippet that calls launchUrl(Uri.parse(url), mode: LaunchMode.externalApplication). Attach this action to the 'Connect Account' button's Actions panel → + Add Action → Custom Action → LaunchUrl, passing in the onboarding_url from App State. This opens Stripe's flow in the device's default browser. Option B (more integrated): add a WebView widget from the FlutterFlow widget panel (it's in the Layout / Container elements; search 'WebView'). Bind its URL property to the onboarding_url App State variable and place it on a dedicated OnboardingWebView page. Navigate to this page after the CreateAccount API call succeeds. When the seller completes onboarding, Stripe redirects to your return_url (myapp://onboarding/complete). Handle this deep link in your app by navigating back to the seller dashboard and calling GetAccountStatus to confirm charges_enabled and payouts_enabled are both true. Display a success banner when they are. If the seller abandons, Stripe redirects to refresh_url — call CreateAccount again to generate a new link.
1// Custom Action: LaunchUrl (url_launcher ^6.2.6)2import 'package:url_launcher/url_launcher.dart';34Future launchUrl(String url) async {5 final uri = Uri.parse(url);6 if (await canLaunchUrl(uri)) {7 await launchUrl(uri, mode: LaunchMode.externalApplication);8 } else {9 throw Exception('Could not launch $url');10 }11}Pro tip: If you'd rather skip building the deep-link handling yourself, RapidDev's team builds FlutterFlow integrations like this every week — free scoping call at rapidevelopers.com/contact.
Expected result: Tapping 'Connect your bank account' opens the Stripe Express onboarding flow. After completion the app returns to the seller dashboard and shows 'Account connected' when GetAccountStatus returns charges_enabled: true.
Collect payment and set the marketplace fee split
When a buyer is ready to pay, your app needs to: (1) call CreatePaymentIntent on your proxy, passing the correct amount in cents (important: $29.99 = 2999, NOT 29.99 — this is the single most common beginner bug with Stripe), the destination connected account ID, and the application_fee_amount also in cents; (2) receive the client_secret back; (3) use the Stripe Flutter SDK to present the Payment Sheet to the buyer. In FlutterFlow, go to Custom Code → + Add → Action, name it ShowPaymentSheet, add the package flutter_stripe (^10.1.1) to Dependencies. In the Dart action, initialise Stripe with your publishable key (safe to hardcode or store in App Values), pass the client_secret received from the proxy into Stripe.instance.initPaymentSheet(), then call Stripe.instance.presentPaymentSheet(). The amount shown to the buyer is the full charge amount. The proxy handles the split: application_fee_amount goes to your platform account, (amount - application_fee_amount - Stripe fees) goes to transfer_data[destination]. Wire this Custom Action to your 'Pay Now' button in the Action Flow Editor. After presentPaymentSheet() completes without error, write the transaction record (PaymentIntent ID, seller ID, amount, status 'pending') to Firestore or Supabase from the app — you'll update it to 'succeeded' when the webhook fires.
1// Custom Action: ShowPaymentSheet (flutter_stripe ^10.1.1)2import 'package:flutter_stripe/flutter_stripe.dart';34Future<bool> showPaymentSheet(5 String clientSecret,6 String publishableKey,7) async {8 Stripe.publishableKey = publishableKey;9 await Stripe.instance.initPaymentSheet(10 paymentSheetParameters: SetupPaymentSheetParameters(11 paymentIntentClientSecret: clientSecret,12 merchantDisplayName: 'Your Platform Name',13 ),14 );15 await Stripe.instance.presentPaymentSheet();16 return true; // payment collected17}Pro tip: Always convert dollar amounts to cents in FlutterFlow before passing them to the API Call. Use a Formula in the variable binding: multiply the price variable by 100 and convert to int. Sending 29.99 instead of 2999 charges a fraction of the intended amount.
Expected result: Tapping 'Pay Now' presents the Stripe Payment Sheet on the device. After the buyer completes payment, the action returns true and the app writes a pending transaction record.
Handle webhooks and stream payout status into the app
Stripe sends asynchronous webhook events to your backend when a payment succeeds (payment_intent.succeeded), when funds transfer to the seller (transfer.created), and when a connected account's status changes (account.updated). FlutterFlow cannot receive webhooks directly — they require a persistent HTTPS endpoint. Add a webhook handler to your Firebase or Supabase function. Register its URL in the Stripe Dashboard under Developers → Webhooks → Add endpoint. In the handler, verify the webhook signature using stripe.webhooks.constructEvent(body, signature, webhookSecret) — the webhookSecret is the whsec_* value Stripe shows you when you add the endpoint. On a verified payment_intent.succeeded event, update the transaction document in Firestore or the row in Supabase from status 'pending' to 'succeeded'. In FlutterFlow, set up a Firestore stream listener or a Supabase realtime subscription on the transactions table filtered to the current user. Bind the status field to a Text widget on the seller dashboard — it updates automatically when the webhook fires. For the buyer, show an 'Order confirmed' screen once the stream emits 'succeeded'. Test the entire flow in Stripe's test mode using test card 4242 4242 4242 4242 with any future expiry and CVC before switching to live mode.
1// Webhook handler addition to the Firebase Cloud Function (index.js)2app.post('/webhook', express.raw({ type: 'application/json' }), async (req, res) => {3 const sig = req.headers['stripe-signature'];4 let event;5 try {6 event = stripe.webhooks.constructEvent(7 req.body,8 sig,9 functions.config().stripe.webhook_secret10 );11 } catch (err) {12 return res.status(400).send(`Webhook Error: ${err.message}`);13 }14 if (event.type === 'payment_intent.succeeded') {15 const pi = event.data.object;16 await admin.firestore()17 .collection('transactions')18 .doc(pi.id)19 .update({ status: 'succeeded', settled_at: new Date() });20 }21 res.json({ received: true });22});Pro tip: Use a unique Stripe PaymentIntent ID (pi_xxx) as the document ID in Firestore so the webhook can find and update the right record without scanning the collection.
Expected result: When a test payment completes, the Stripe Dashboard shows the webhook delivered successfully, Firestore updates the transaction status to 'succeeded', and the FlutterFlow app's stream widget refreshes to display 'Payment confirmed'.
Common use cases
Freelancer marketplace app where clients pay and freelancers get their cut
A FlutterFlow app lets clients post jobs and hire freelancers. When the client pays, the platform takes a 10-15% fee and transfers the rest directly to the freelancer's Stripe Express account. The app surfaces the freelancer's payout balance pulled from the proxy.
Build a marketplace where clients tap 'Pay' for a job, the app calls my backend to charge the client and split the payment — platform keeps 12%, freelancer gets 88% — and shows the freelancer their pending balance.
Copy this prompt to try it in FlutterFlow
Creator tip jar app with multi-creator payouts
Fans can browse creator profiles and send tips directly. Each creator onboards via a Stripe Express account link shown in the app. Tips flow through the platform (which keeps a small fee) before settling in each creator's bank account.
Let users send a tip to any creator. Each creator should have connected their Stripe account via an onboarding screen. Show the creator a running balance of tips received.
Copy this prompt to try it in FlutterFlow
Service booking app paying contractors on completion
Customers book and prepay for a home service. The platform holds funds until the contractor marks the job done, then releases payment minus the platform fee to the contractor's Stripe account. The app shows the contractor a history of completed payouts.
Build a booking screen where customers prepay. After the contractor confirms job completion in the app, automatically release 85% of the payment to the contractor and keep 15% as the platform fee.
Copy this prompt to try it in FlutterFlow
Troubleshooting
API call returns 401 Unauthorized or 'No such API key' from Stripe
Cause: The secret key passed in the Authorization header inside the proxy is wrong, missing, or comes from a different Stripe environment (test key used against live endpoint or vice versa).
Solution: Check your Firebase Functions config (firebase functions:config:get) or Supabase secrets to confirm the sk_* value is set and matches the environment your proxy is targeting (test vs live). Ensure the Authorization header in all Stripe calls reads 'Bearer ' + your sk_* key.
Transfer fails with 'The destination account does not have the transfers capability'
Cause: The seller's Express account has not completed onboarding, or the 'transfers' capability has not been requested when the account was created.
Solution: Call the GetAccountStatus endpoint and check that both charges_enabled and payouts_enabled are true before attempting a transfer. If they are false, re-generate an Account Link and send the seller back through onboarding. When creating the account, ensure you pass capabilities: { card_payments: { requested: true }, transfers: { requested: true } }.
Seller is stuck on Stripe's onboarding page and the app never returns
Cause: The return_url or refresh_url passed to /v1/account_links does not match a registered deep link scheme in the iOS or Android app, so Stripe's redirect has nowhere to go.
Solution: In FlutterFlow, go to Settings → App Details → Deep Linking and set up your custom URI scheme (e.g. myapp://). Make sure the return_url you pass to your proxy matches exactly — e.g. myapp://onboarding/complete. On iOS, also register the scheme in Info.plist under CFBundleURLSchemes via FlutterFlow's platform settings. Test on a real device, not the web preview.
Payment amount is drastically wrong (seller receives $0.50 when they expected $50.00)
Cause: The amount was passed in dollars as a decimal (50.00) instead of cents as an integer (5000). Stripe interprets the value as the smallest currency unit.
Solution: In your FlutterFlow API Call variable binding for amount, apply a formula that multiplies the price by 100 and floors it to an integer (e.g. (price * 100).toInt()). Verify the value in the Request & Test tab before going live.
Best practices
- Never place your Stripe sk_* key in a FlutterFlow API Call header, App Value, or Custom Action — route all secret-key operations through a Firebase Cloud Function or Supabase Edge Function.
- Use Stripe test mode (pk_test_* / sk_test_*) with test card 4242 4242 4242 4242 for all development and staging flows before switching to live keys.
- Always pass amounts in the smallest currency unit (cents for USD): multiply your dollar price by 100 and convert to integer before sending to the API.
- Set a unique idempotency key on PaymentIntent creation (Idempotency-Key header) to prevent duplicate charges if the user double-taps the Pay button or the network retries the request.
- Verify every Stripe webhook signature using stripe.webhooks.constructEvent() before trusting the event data — an unverified webhook endpoint can be spoofed to mark orders as paid.
- Store each seller's connected account ID (acct_*) in Firestore or Supabase keyed to their user ID, never in FlutterFlow App State — App State is ephemeral and resets on app restart.
- Check charges_enabled and payouts_enabled on the seller's account before creating a PaymentIntent with that account as the destination; show a clear 'Finish account setup' CTA if either is false.
- Use Stripe's restricted API keys in your proxy (permission-scoped to Connect, accounts, payment intents only) rather than the full-access secret key, to limit exposure.
Alternatives
Braintree is natively supported in FlutterFlow's Payments settings and is simpler for single-merchant checkout, but does not support marketplace splits like Stripe Connect does.
PayPal Payouts sends money to recipients by email and is great for mass disbursements, but it's a payout tool — not a split-at-checkout marketplace flow like Stripe Connect.
Adyen supports marketplace splits via its Marketpay product at enterprise scale, but has volume minimums and no native FlutterFlow support, making it a heavier lift than Stripe Connect.
Frequently asked questions
Can I use FlutterFlow's built-in Stripe Payments toggle for a marketplace?
No. The native FlutterFlow Stripe integration handles single-merchant checkouts where all funds go to one Stripe account. Stripe Connect — which enables splitting payments across multiple sellers — must be built manually through a backend proxy and API Calls as described in this guide. The two flows are architecturally separate.
Does this work in FlutterFlow's web Test Mode?
Partially. The API Calls to your proxy will work in Test Mode for basic request/response testing. However, the Stripe Payment Sheet (via the flutter_stripe Custom Action) only works on a real iOS or Android device or a physical build — it will not render in the FlutterFlow canvas preview or web Run Mode. Test your payment flow on a device with a Stripe test card.
What Stripe Connect account type should I use for sellers?
Express accounts are the best starting point for most FlutterFlow marketplaces. Stripe hosts the onboarding and identity verification, and sellers see 'Powered by Stripe' branding — you don't need to build any KYC flows. Custom accounts give you full control over the UX and branding but require you to handle compliance, identity verification, and bank account collection yourself, which is significantly more work.
How do I handle the case where a seller hasn't finished onboarding yet?
Call your GetAccountStatus endpoint before creating any PaymentIntent targeting that seller. If charges_enabled or payouts_enabled is false, show the seller a 'Complete your account setup' button that calls CreateAccount again (Stripe generates a fresh Account Link) and re-opens the onboarding WebView. Don't let buyers attempt to pay a seller whose account isn't ready — the PaymentIntent will fail.
Can I use Stripe Connect in Stripe test mode without real bank accounts?
Yes. In test mode you can create test Express accounts using Stripe's test onboarding (it bypasses real identity verification) and use test routing/account numbers for bank details. Transfers between test accounts happen instantly and don't move real money. Run all your flows in test mode before flipping to live keys.
My webhook is not firing in Stripe's Dashboard. What should I check?
First confirm the endpoint URL in Stripe Dashboard → Developers → Webhooks matches the exact path your function listens on (e.g. /webhook). Second, make sure your Firebase or Supabase function is deployed and publicly accessible — test it with a manual webhook delivery from the Stripe Dashboard. Third, check that the function is using express.raw() (not express.json()) to parse the webhook body, because Stripe's signature verification requires the raw bytes.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation