●TOOLING — Rork's developer repos keep moving: rork-xcode was updated on July 16, rork-device on July 15, and rork-plist on July 13●OPUS46 — Claude Opus 4.6 is live in Rork, and Rork Max is built to assemble apps on top of Claude Code●SIM — A cloud iOS simulator runs in the browser, with one click to install on a device and two clicks to publish to the App Store●MAX — Rork Max emits pure Swift rather than React Native, reaching iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and even iMessage●NATIVE — That opens up HealthKit, ARKit and LiDAR, NFC, Dynamic Island, Live Activities, 3D through Metal, and on-device inference with Core ML●SEED — Rork raised a $15M seed led by Left Lane Capital, with Peak XV and a16z Speedrun joining the round●TOOLING — Rork's developer repos keep moving: rork-xcode was updated on July 16, rork-device on July 15, and rork-plist on July 13●OPUS46 — Claude Opus 4.6 is live in Rork, and Rork Max is built to assemble apps on top of Claude Code●SIM — A cloud iOS simulator runs in the browser, with one click to install on a device and two clicks to publish to the App Store●MAX — Rork Max emits pure Swift rather than React Native, reaching iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and even iMessage●NATIVE — That opens up HealthKit, ARKit and LiDAR, NFC, Dynamic Island, Live Activities, 3D through Metal, and on-device inference with Core ML●SEED — Rork raised a $15M seed led by Left Lane Capital, with Peak XV and a16z Speedrun joining the round
Setup and context: Why Marketplace Apps Are Still a Strong Bet in 2026
Peer-to-peer commerce — freelance platforms, resale apps, coaching marketplaces, sharing economy services — remains one of the most compelling business models for indie developers. The core appeal is simple: as your user base grows, your platform fee revenue scales automatically. You build the infrastructure once and collect a percentage of every transaction that flows through it.
But there's a technical hurdle that trips up many developers: standard Stripe integration doesn't handle the "collect from buyer, distribute to seller, keep a cut" flow. For that, you need Stripe Connect.
This guide walks through building a full-featured marketplace payment system using Rork (and Rork Max) with Supabase as the backend. We'll go from account architecture all the way to production launch — with real, working code at every step.
What You'll Learn
The three Stripe Connect account types and which one fits a marketplace
Implementing the seller onboarding screen in your Rork app
Backend database design with Supabase (sellers, products, orders)
Processing payments and distributing fees with the Destination Charges API
Automating order status updates via Webhooks
A pre-launch compliance checklist
1. Stripe Connect Fundamentals: Choosing the Right Account Type
Stripe Connect offers three account types, and your choice will shape your entire architecture.
Standard accounts give sellers their own full Stripe dashboard. They manage their account independently, and Stripe handles onboarding UI out of the box. Implementation is low-cost, but you have limited programmatic control over seller accounts.
Express accounts are the sweet spot for most marketplaces. Stripe hosts a streamlined onboarding flow and handles KYC (Know Your Customer) verification on your behalf, which dramatically reduces your compliance burden. They pair naturally with Destination Charges. For solo-developed marketplaces, Express is the right default.
Custom accounts offer complete UI control, but you take on KYC responsibility and the associated legal liability. This is not appropriate for early-stage products.
We'll use Express accounts with Destination Charges throughout this guide.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦Developers stuck on Stripe Connect setup can implement the complete seller account registration, KYC, and onboarding flow from Rork today
✦Get copy-paste-ready code for marketplace fee distribution and automated seller payouts using Destination Charges
✦From Supabase integration and Webhook handling to a production launch checklist — everything you need to ship a monetizable marketplace as a solo developer
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
We'll use Supabase as the backend. Start by creating the tables you'll need.
Table Schema
-- Sellers table (linked to Stripe Connect accounts)create table sellers ( id uuid primary key default gen_random_uuid(), user_id uuid references auth.users(id) not null, stripe_account_id text unique, -- Stripe Connect account ID (acct_xxxx) onboarding_complete boolean default false, charges_enabled boolean default false, payouts_enabled boolean default false, created_at timestamptz default now());-- Products tablecreate table products ( id uuid primary key default gen_random_uuid(), seller_id uuid references sellers(id) not null, title text not null, description text, price integer not null, -- Price in smallest currency unit currency text default 'usd', image_url text, status text default 'active', -- active / sold / deleted created_at timestamptz default now());-- Orders tablecreate table orders ( id uuid primary key default gen_random_uuid(), product_id uuid references products(id) not null, buyer_user_id uuid references auth.users(id) not null, seller_id uuid references sellers(id) not null, amount integer not null, -- Total charge amount platform_fee integer not null, -- Platform's cut stripe_payment_intent_id text unique, stripe_transfer_id text, status text default 'pending', -- pending / paid / shipped / completed / refunded created_at timestamptz default now());
Row Level Security
-- Sellers can only access their own recordalter table sellers enable row level security;create policy "sellers_own_record" on sellers for all using (auth.uid() = user_id);-- Products are publicly readable; only seller can modifyalter table products enable row level security;create policy "products_read_all" on products for select using (status = 'active');create policy "products_write_own" on products for all using ( seller_id in (select id from sellers where user_id = auth.uid()) );-- Orders visible only to buyer and selleralter table orders enable row level security;create policy "orders_participant_only" on orders for select using ( buyer_user_id = auth.uid() or seller_id in (select id from sellers where user_id = auth.uid()) );
3. Creating a Stripe Connect Account and Onboarding
Here's the flow: seller taps "Register as Seller" → your backend creates a Stripe Express account → Stripe serves their KYC screen → seller completes verification → your app gets notified via Webhook.
4. Destination Charges: Collecting Payment and Splitting Fees
With Destination Charges, a single API call handles everything: collect from the buyer, route funds to the seller, and retain your platform fee automatically.
Without Webhooks, orders stay in "pending" indefinitely after successful payment. This is the glue that keeps your database consistent with Stripe's state.
// supabase/functions/stripe-webhook/index.tsimport { serve } from "https://deno.land/std@0.168.0/http/server.ts";import Stripe from "https://esm.sh/stripe@14.0.0";import { createClient } from "https://esm.sh/@supabase/supabase-js@2";const stripe = new Stripe(Deno.env.get("STRIPE_SECRET_KEY")\!, { apiVersion: "2024-06-20", httpClient: Stripe.createFetchHttpClient(),});serve(async (req) => { const body = await req.text(); const signature = req.headers.get("stripe-signature")\!; const webhookSecret = Deno.env.get("STRIPE_WEBHOOK_SECRET")\!; let event: Stripe.Event; try { // Must use async version in Deno/Cloudflare Workers environments event = await stripe.webhooks.constructEventAsync( body, signature, webhookSecret, undefined, Stripe.createSubtleCryptoProvider() ); } catch (err) { console.error("Webhook verification failed:", err); return new Response("Invalid signature", { status: 400 }); } const supabase = createClient( Deno.env.get("SUPABASE_URL")\!, Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")\! ); switch (event.type) { case "payment_intent.succeeded": { const pi = event.data.object as Stripe.PaymentIntent; await supabase .from("orders") .update({ status: "paid" }) .eq("stripe_payment_intent_id", pi.id); const { data: order } = await supabase .from("orders") .select("product_id") .eq("stripe_payment_intent_id", pi.id) .single(); if (order) { await supabase .from("products") .update({ status: "sold" }) .eq("id", order.product_id); } break; } case "payment_intent.payment_failed": { const pi = event.data.object as Stripe.PaymentIntent; await supabase .from("orders") .update({ status: "failed" }) .eq("stripe_payment_intent_id", pi.id); break; } case "account.updated": { const account = event.data.object as Stripe.Account; await supabase .from("sellers") .update({ charges_enabled: account.charges_enabled, payouts_enabled: account.payouts_enabled, onboarding_complete: account.details_submitted, }) .eq("stripe_account_id", account.id); break; } default: console.log(`Unhandled event: ${event.type}`); } return new Response(JSON.stringify({ received: true }), { headers: { "Content-Type": "application/json" }, });});
6. Seller Dashboard: Balance and Payout History
Give sellers visibility into their earnings with a simple dashboard endpoint.
Refunds on Destination Charges require reversing the transfer to the seller. Forget this step and the money stays with the seller even after the refund is issued.
// supabase/functions/process-refund/index.ts (admin-only endpoint)import { serve } from "https://deno.land/std@0.168.0/http/server.ts";import Stripe from "https://esm.sh/stripe@14.0.0";import { createClient } from "https://esm.sh/@supabase/supabase-js@2";const stripe = new Stripe(Deno.env.get("STRIPE_SECRET_KEY")\!, { apiVersion: "2024-06-20",});serve(async (req) => { const { orderId, reason } = await req.json(); const supabase = createClient( Deno.env.get("SUPABASE_URL")\!, Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")\! ); const { data: order } = await supabase .from("orders") .select("*") .eq("id", orderId) .single(); if (\!order || order.status \!== "paid") { return new Response("Order not eligible for refund", { status: 400 }); } const refund = await stripe.refunds.create({ payment_intent: order.stripe_payment_intent_id, reason: reason ?? "requested_by_customer", reverse_transfer: true, // Pull funds back from the seller refund_application_fee: true, // Refund the platform fee too }); await supabase .from("orders") .update({ status: "refunded" }) .eq("id", orderId); await supabase .from("products") .update({ status: "active" }) .eq("id", order.product_id); return new Response( JSON.stringify({ refundId: refund.id, status: refund.status }), { headers: { "Content-Type": "application/json" } } );});
8. Pre-Launch Compliance Checklist
Don't skip this. Marketplaces are subject to tighter scrutiny than standard payment processors.
Stripe Dashboard Configuration — In your Connect settings, fill in your platform's business description, support email, and branding. This text appears on the seller onboarding screen. Accept the Stripe Connect service agreement (required for live mode).
Testing Checklist
Run through each of these in test mode before going live: complete the buyer purchase flow using card 4242 4242 4242 4242, verify that Webhooks are received and orders update correctly (use stripe listen --forward-to locally), test the refund flow end-to-end, and confirm that account.updated events with charges_enabled: true correctly update your sellers table.
Security Checklist
Confirm that RLS is enabled on all tables, that your secret keys are stored in environment variables (never in source code), that Webhook signature verification is active, and that admin-only endpoints (like refunds) have authentication that prevents sellers and buyers from calling them directly.
Legal Considerations
Platform businesses that temporarily hold funds may be subject to money transmission regulations, which vary by country. In the US, this depends on your state and business structure. Before launching, consult a legal professional to understand your obligations. Stripe's documentation on their regulatory compliance framework is a good starting point.
9. Frequently Asked Questions
Q: How long does Stripe Express account verification take?
For individuals in the US, uploading a government-issued ID through Stripe's onboarding flow typically results in approval within 1–3 business days. For businesses, it may take longer. Your account.updated Webhook will fire with charges_enabled: true once the seller is approved.
Q: What platform fee percentage is reasonable?
Resale and physical goods marketplaces typically charge 8–15%. Service and digital goods platforms often range from 15–25%. Factor in Stripe's own processing fee (2.9% + 30¢ in the US) when setting your rate. At 10% platform fee, your net margin after Stripe fees is roughly 6–7%.
Q: Can I support multiple currencies?
Yes. Stripe Connect supports multi-currency payouts. Unlike JPY (which has no decimal), most currencies require amounts in the smallest unit (cents). If you're building a global marketplace, you'll need currency conversion logic and should specify the payout currency explicitly in transfer_data.
Q: What if a seller registers but never completes KYC?
Sellers with incomplete onboarding will have charges_enabled: false in your database. Your payment API should check this before creating a PaymentIntent and return an appropriate error. You can also send reminder notifications via Supabase Edge Functions or a third-party email provider.
Q: How do I prevent buyers from purchasing their own listings?
In your payment Edge Function, check that user.id \!== product.seller_user_id before proceeding. You can also enforce this at the RLS level on the orders table. Displaying a "This is your listing" message in the Rork UI is good UX reinforcement.
10. Advanced Patterns for Production Marketplaces
Once the core payment flow is working, these patterns will make your marketplace significantly more robust and seller-friendly.
Escrow-Style Delayed Payouts
For physical goods or service marketplaces, you may want to hold funds until the buyer confirms receipt. Use capture_method: "manual" to authorize without immediately capturing:
// Create PaymentIntent with manual capture (7-day authorization window)const paymentIntent = await stripe.paymentIntents.create({ amount, currency: "usd", transfer_data: { destination: sellerAccountId }, application_fee_amount: platformFee, capture_method: "manual", metadata: { product_id: productId },});// When buyer confirms receipt — capture the authorized fundsawait stripe.paymentIntents.capture(paymentIntent.id);// If there's a dispute before capture — cancel without chargeawait stripe.paymentIntents.cancel(paymentIntent.id);
This gives buyers meaningful protection without requiring a custom escrow service.
Tiered Commission Rates
Reward your best sellers with lower fees. Make the platform fee rate a function of the seller's sales history:
Tiered fees incentivize high-volume sellers to stay on your platform rather than seeking cheaper alternatives.
Seller Onboarding Reminders
Sellers who start but don't finish KYC are a significant source of lost supply. A scheduled Edge Function can generate fresh onboarding links and send reminders:
// Identify sellers who started onboarding 24+ hours ago but haven't completed itconst { data: pendingSellers } = await supabase .from("sellers") .select("user_id, stripe_account_id, created_at") .eq("onboarding_complete", false) .not("stripe_account_id", "is", null) .lt("created_at", new Date(Date.now() - 86_400_000).toISOString());for (const seller of pendingSellers ?? []) { // AccountLinks expire; always generate a fresh one const link = await stripe.accountLinks.create({ account: seller.stripe_account_id, refresh_url: `${APP_URL}/seller/onboarding?reauth=true`, return_url: `${APP_URL}/seller/onboarding?success=true`, type: "account_onboarding", }); await sendReminderEmail(seller.user_id, link.url);}
Audit Logging for Compliance
Every marketplace that handles real money should maintain an append-only audit log:
// Call this on every order status transitionawait supabase.from("order_audit_log").insert({ order_id: orderId, old_status: previousStatus, new_status: newStatus, source: "webhook", stripe_event_id: event.id, recorded_at: new Date().toISOString(),});
When a buyer disputes a charge, this log is the difference between resolving the case in minutes versus hours.
11. Monitoring and Operations
Running a live marketplace requires ongoing visibility into payment health and the ability to respond quickly when things go wrong.
Stripe Dashboard Alerts — Enable Radar rules to flag unusual patterns (large transactions from new accounts, repeated failed attempts from the same IP). Subscribe to account.application.deauthorized events to know immediately when a seller disconnects your platform. Monitor your Webhook event delivery health under Developers → Webhooks in the Stripe dashboard.
Metrics That Matter
Track these figures weekly. Gross Merchandise Value (GMV) is total payment volume before fees — the headline number for any marketplace. Take Rate is your actual net revenue as a percentage of GMV; it fluctuates with tiered pricing and refunds. Seller Activation Rate measures what proportion of registered sellers complete KYC and make at least one sale. Authorization Rate is the percentage of payment attempts that succeed — a rate below 90% suggests friction in your checkout UX or risk signals in your transactions.
Stripe Outage Protocol
When Stripe experiences degradation (monitor status.stripe.com), Webhook delivery may be delayed. Never confirm orders to buyers based solely on client-side success callbacks. Always wait for the payment_intent.succeeded Webhook before updating order status, triggering fulfillment, or sending confirmation emails. Building this delay tolerance into your UX from the start will save you painful retrofits later.
A Note from an Indie Developer
Wrapping Up
Building a marketplace payment system is genuinely one of the more complex tasks in mobile development — but Rork's speed of iteration, combined with Stripe Connect's mature API and Supabase's edge-deployable functions, makes it achievable for solo developers who couldn't have attempted it a few years ago.
Let's recap what we covered. We started with account architecture — choosing Express accounts to let Stripe own the KYC burden, so you can focus on building product rather than navigating identity verification regulations. We implemented the full seller onboarding flow using Stripe's hosted AccountLink UI, which handles the complex document collection and verification steps without any custom code on your end.
For payments, we used Destination Charges to create PaymentIntents that automatically split funds: the buyer pays one amount, the seller receives their portion minus your platform fee, and the fee stays in your platform account without any manual reconciliation. The split happens atomically — either all of it succeeds or none of it does.
Webhook processing is where many marketplace implementations break down. We built a handler that listens for payment_intent.succeeded, payment_intent.payment_failed, and account.updated events to keep your database in sync with Stripe's state. Without this, you'll have ghost orders and frustrated sellers who think they're approved but can't actually receive payments.
On the operational side, we covered refund mechanics (the critical reverse_transfer: true parameter that actually pulls funds back from the seller), seller dashboard data fetched directly from Stripe's balance and payout APIs, and advanced patterns like escrow-style delayed capture and tiered commission rates.
Finally, the compliance checklist. Marketplaces sit in a legally sensitive space — you're moving money on behalf of third parties, which may trigger money transmission regulations depending on your jurisdiction. That's not a reason to not build; it's a reason to get informed early. Stripe's Express account model is deliberately designed to reduce the regulatory surface area for platforms, but you should still consult a legal professional before going live.
The architecture in this guide is production-ready and has been battle-tested across real marketplace deployments. Start with the minimum viable implementation — seller onboarding, a payment flow, and Webhook handling — and layer in the advanced patterns as your marketplace grows.
For more on Stripe integration patterns in Rork, see the Rork × Stripe Subscription Complete Guide. For backend design with Supabase, the Rork × Supabase Auth and Realtime Guide covers the fundamentals in depth.
Share
Thank You for Reading
Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.