Stripe Connect lets marketplaces route payments between buyers, sellers, and the platform. Choose between Standard (seller manages Stripe), Express (simplified onboarding), or Custom (full control) account types. This guide covers all three approaches with destination charges, direct charges, and separate charges-and-transfers for multi-party payment flows.
Building Marketplace Payments with Stripe Connect
Stripe Connect is the infrastructure layer for marketplace and platform payments. It handles the complexity of moving money between multiple parties — buyers, sellers, and your platform — while managing regulatory requirements like KYC and tax reporting. The three account types (Standard, Express, Custom) offer different tradeoffs between onboarding simplicity and platform control.
Prerequisites
- A Stripe account with Connect enabled (Dashboard → Connect → Get started)
- Node.js 18 or later installed
- Stripe Node.js SDK installed: npm install stripe
- Understanding of basic Stripe payments (PaymentIntents)
- Express.js for the server examples: npm install express
Step-by-step guide
Choose your Connect account type
Choose your Connect account type
Standard accounts redirect sellers to Stripe's full dashboard. Express accounts use Stripe-hosted onboarding with a simplified experience. Custom accounts give you full UI control but require you to handle all compliance. For most marketplaces, Express is the best starting point.
Expected result: You have decided on an account type. Express is recommended for most use cases.
Create a connected account
Create a connected account
Use the API to create a connected account for each seller on your platform.
1const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);23async function createConnectedAccount() {4 const account = await stripe.accounts.create({5 type: 'express',6 country: 'US',7 email: 'seller@example.com',8 capabilities: {9 card_payments: { requested: true },10 transfers: { requested: true },11 },12 });13 console.log('Connected account created:', account.id);14 return account;15}1617createConnectedAccount();Expected result: A new Express connected account is created with an ID like acct_1ABC123.
Generate an onboarding link
Generate an onboarding link
Create an Account Link that redirects the seller to Stripe's hosted onboarding flow where they provide identity and banking details.
1async function createOnboardingLink(accountId) {2 const accountLink = await stripe.accountLinks.create({3 account: accountId,4 refresh_url: 'https://yoursite.com/onboarding/refresh',5 return_url: 'https://yoursite.com/onboarding/complete',6 type: 'account_onboarding',7 });8 console.log('Onboarding URL:', accountLink.url);9 return accountLink.url;10}1112createOnboardingLink('acct_1ABC123');Expected result: A URL is returned that redirects the seller to Stripe's onboarding form.
Create a destination charge with platform fee
Create a destination charge with platform fee
Charge the buyer and route funds to the seller's connected account. Use application_fee_amount to collect your platform commission.
1async function createDestinationCharge(connectedAccountId) {2 const paymentIntent = await stripe.paymentIntents.create({3 amount: 10000, // $100.00 in cents4 currency: 'usd',5 payment_method_types: ['card'],6 application_fee_amount: 1500, // $15.00 platform fee7 transfer_data: {8 destination: connectedAccountId,9 },10 });11 console.log('PaymentIntent created:', paymentIntent.id);12 return paymentIntent;13}1415createDestinationCharge('acct_1ABC123');Expected result: A PaymentIntent is created. When confirmed, $85.00 goes to the seller and $15.00 to your platform.
Use separate charges and transfers for multi-seller orders
Use separate charges and transfers for multi-seller orders
For orders involving multiple sellers, charge the buyer once and then create separate transfers to each seller.
1async function chargeAndTransfer() {2 // Step 1: Charge the buyer on your platform3 const paymentIntent = await stripe.paymentIntents.create({4 amount: 20000, // $200.005 currency: 'usd',6 payment_method_types: ['card'],7 });89 // Step 2: Transfer to each seller after payment succeeds10 const transfer1 = await stripe.transfers.create({11 amount: 8000, // $80.00 to seller A12 currency: 'usd',13 destination: 'acct_sellerA',14 source_transaction: paymentIntent.latest_charge,15 });1617 const transfer2 = await stripe.transfers.create({18 amount: 10000, // $100.00 to seller B19 currency: 'usd',20 destination: 'acct_sellerB',21 source_transaction: paymentIntent.latest_charge,22 });2324 console.log('Transfers created:', transfer1.id, transfer2.id);25 // Platform keeps $20.0026}2728chargeAndTransfer();Expected result: One charge to the buyer and two separate transfers to different sellers. Your platform retains the remainder.
Complete working example
1const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);2const express = require('express');3const app = express();45// Create a connected Express account for a new seller6app.post('/api/create-seller', async (req, res) => {7 try {8 const account = await stripe.accounts.create({9 type: 'express',10 country: req.body.country || 'US',11 email: req.body.email,12 capabilities: {13 card_payments: { requested: true },14 transfers: { requested: true },15 },16 });1718 const accountLink = await stripe.accountLinks.create({19 account: account.id,20 refresh_url: `${req.headers.origin}/onboarding/refresh`,21 return_url: `${req.headers.origin}/onboarding/complete`,22 type: 'account_onboarding',23 });2425 res.json({ accountId: account.id, onboardingUrl: accountLink.url });26 } catch (err) {27 res.status(400).json({ error: err.message });28 }29});3031// Create a destination charge (single seller)32app.post('/api/charge', async (req, res) => {33 try {34 const { amount, sellerAccountId, platformFee } = req.body;35 const paymentIntent = await stripe.paymentIntents.create({36 amount: amount, // in cents37 currency: 'usd',38 payment_method_types: ['card'],39 application_fee_amount: platformFee,40 transfer_data: {41 destination: sellerAccountId,42 },43 });44 res.json({ clientSecret: paymentIntent.client_secret });45 } catch (err) {46 res.status(400).json({ error: err.message });47 }48});4950// Check connected account status51app.get('/api/seller/:accountId', async (req, res) => {52 try {53 const account = await stripe.accounts.retrieve(req.params.accountId);54 res.json({55 payoutsEnabled: account.payouts_enabled,56 chargesEnabled: account.charges_enabled,57 requirements: account.requirements.currently_due,58 });59 } catch (err) {60 res.status(400).json({ error: err.message });61 }62});6364// Webhook for Connect events65app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {66 const sig = req.headers['stripe-signature'];67 let event;68 try {69 event = stripe.webhooks.constructEvent(70 req.body, sig, process.env.STRIPE_WEBHOOK_SECRET71 );72 } catch (err) {73 return res.status(400).send(`Webhook Error: ${err.message}`);74 }7576 switch (event.type) {77 case 'account.updated':78 console.log('Account updated:', event.data.object.id);79 break;80 case 'payment_intent.succeeded':81 console.log('Payment succeeded:', event.data.object.id);82 break;83 }84 res.json({ received: true });85});8687app.use(express.json());88app.listen(3000, () => console.log('Marketplace server on port 3000'));Common mistakes when using Stripe Connect for marketplace payments
Why it's a problem: Using direct charges when destination charges would be simpler
How to avoid: Destination charges are simpler for most marketplaces. Use direct charges only when the connected account should be the merchant of record.
Why it's a problem: Not checking charges_enabled before routing payments to a connected account
How to avoid: Always verify account.charges_enabled is true before creating charges on behalf of a connected account.
Why it's a problem: Hardcoding Account Link URLs instead of generating fresh ones
How to avoid: Account Links expire quickly. Always generate a new one when the seller initiates onboarding.
Why it's a problem: Forgetting to request capabilities when creating connected accounts
How to avoid: Always request card_payments and transfers capabilities. Without them, the account cannot accept payments or receive transfers.
Best practices
- Start with Express accounts — they handle compliance UI and reduce your development burden
- Always collect application_fee_amount on charges rather than transferring fees separately
- Use webhooks to track connected account status changes instead of polling
- Store the connected account ID in your database alongside the seller's user record
- Test the full onboarding flow in test mode using Stripe's test data before going live
- Implement idempotency keys on charge and transfer creation to prevent duplicate transactions
- For complex marketplace builds, teams like RapidDev can help architect the optimal Connect integration
- Monitor your Connect dashboard for accounts with overdue requirements
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
Explain the three Stripe Connect account types (Standard, Express, Custom) and when to use each. I'm building a marketplace where sellers list products and buyers pay. Which approach should I use?
Help me set up a full Stripe Connect marketplace with Express accounts. I need seller onboarding, destination charges with platform fees, and webhooks for account status changes. Use Node.js and Express.
Frequently asked questions
What is the difference between destination charges and direct charges?
Destination charges are created on your platform account and funds are transferred to the connected account. Direct charges are created directly on the connected account. Destination charges are simpler; direct charges make the connected account the merchant of record.
Do I need a special Stripe plan to use Connect?
No. Stripe Connect is available on all Stripe accounts at no extra monthly cost. You pay standard processing fees plus any Connect-specific fees like the 0.25% + $0.25 per payout to connected accounts on Express/Custom.
Can connected accounts have their own Stripe Dashboard?
Standard accounts have full Stripe Dashboard access. Express accounts get a simplified dashboard (Express Dashboard). Custom accounts have no Stripe-provided dashboard — you must build your own.
How do I handle refunds in a Connect marketplace?
For destination charges, refund the PaymentIntent on your platform. Stripe automatically reverses the transfer to the connected account. You can optionally reverse the application fee or keep it.
Can I switch account types after creating a connected account?
No. Once a connected account is created with a type (Standard, Express, or Custom), it cannot be changed. You would need to create a new account with the desired type.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation