How to set up Stripe Connect for a multi-vendor marketplace (2026 guide)
Step-by-step integration guide for Stripe Connect: Express accounts, destination charges, onboarding flows, and production bugs to avoid. Includes Node.js and Python code samples.
How to set up Stripe Connect for a multi-vendor marketplace
We recently launched a B2B parts marketplace connecting suppliers to industrial buyers. The technical requirement: each vendor needed to receive direct payouts while we collected a platform fee on every transaction. Stripe Connect solved this with a straightforward integration. This guide walks you through the exact implementation, Express accounts for automated onboarding, KYC flows, payout scheduling, and common production issues we encountered so you can avoid them.
Key takeaways
Complete technical guide: set up Stripe Connect Express accounts, destination charges, webhooks, and KYC flows. Real code, 6-day timeline.
- What Stripe Connect actually does (and when you need it).
- Architecture decisions before you write code.
- Initial Stripe Connect configuration in the Dashboard.
- Creating Express connected accounts programmatically.
- Building the onboarding flow and KYC collection.
What Stripe Connect actually does (and when you need it)
Stripe Connect splits money between your platform and third-party sellers in a single transaction. The alternative is asking each vendor to set up their own merchant account and build custom reconciliation, a pattern that often fails before companies migrate to Connect.
Connect offers three account types. Express accounts handle onboarding and compliance automatically; your vendor clicks one link and Stripe collects tax ID, bank details, and identity verification. We use these for suppliers who want fast setup and don't need a standalone Stripe Dashboard. Standard accounts give vendors full Dashboard access and let them process charges outside your platform, useful for sellers who already use Stripe. Custom accounts put all compliance and UI in your hands, which means you build the onboarding forms and stay responsible for KYC accuracy. Most marketplaces should start with Express unless you have a legal team ready to own compliance.
The revenue model works like this: a buyer pays $1,000, Stripe routes a portion to the vendor's bank account and the remainder to your platform balance, all in one API call. You set the split. Stripe handles the tax reporting.
Architecture decisions before you write code
Before you call a single API endpoint, decide how money moves. These choices lock in your database schema and payout logic.
Charge types: direct charges vs. destination charges
Destination charges put the payment on your platform account, then transfer a portion to the connected account. You own the customer relationship, handle refunds from your balance, and the charge appears on your Stripe Dashboard. Direct charges create the payment on the connected account and pull your fee back to the platform. The connected account owns the dispute liability.
In our experience, destination charges simplify refunds and reporting. If a buyer disputes an order, Stripe debits your platform balance, you claw back the funds from the vendor, and you deal with one reconciliation ledger instead of many. Direct charges make sense only if vendors already have Stripe accounts and want full control of their transaction history.
Express vs. Standard vs. Custom accounts
Express accounts show high completion rates in our testing. Stripe's hosted UI collects every field the IRS and banking partners require, adapts to the vendor's country, and updates automatically when regulations change. Standard accounts typically take longer because vendors must create standalone Stripe accounts first. Custom accounts require you to implement Stripe Identity verification and stay current with FinCEN guidance, unless you have compliance engineers, don't do this.
Pick Express unless a vendor already has a Standard account or you're in a regulated vertical that requires custom due diligence.
Payout timing and reconciliation strategy
Stripe defaults to daily automatic payouts with a rolling window for fraud monitoring. A sale on Monday typically pays out within a few business days. You can adjust this per connected account via the API or let vendors set it in their Express Dashboard.
Many platforms enforce a minimum payout threshold (such as $50) to avoid micro-transfers that cost more in accounting time than they move. Set this in your Account creation parameters as settings[payouts][schedule][delay_days] and settings[payouts][schedule][monthly_anchor] if you need monthly batches. Build a reconciliation table that logs every charge.succeeded, application_fee.created, and payout.paid webhook so your finance team can trace dollars without parsing Stripe's Dashboard exports.
Initial Stripe Connect configuration in the Dashboard
Log into your Stripe Dashboard, click Connect in the left nav, then Get started. Stripe will ask for your platform's business type, this shows up in vendor-facing emails and onboarding screens, so use your legal entity name, not a working title.
Enabling Connect and setting your platform profile
Under Settings → Connect settings, upload a square logo of appropriate resolution (at least 256×256 pixels is recommended). This appears at the top of the Express onboarding flow. Set your Brand color to your primary hex code; Stripe tints buttons and headers to match. Upload an Icon for mobile onboarding. These branding assets are important, if you skip them, vendors see Stripe's default styling, which can reduce trust.
Fill out Support details with a working email and phone number. When a vendor hits "Contact support" during onboarding, Stripe shows this. Consider using a shared support alias that routes to your team.
Branding settings for Express onboarding
Navigate to Connect → Settings → Branding. Enable Custom branding if you're on a paid Stripe plan. Check the preview on the right, your logo should render cleanly at different resolutions. Set Business name to the exact string you want vendors to see: "Acme Marketplace" or "Acme Inc." Avoid ampersands and special characters; some banks reject payouts if the descriptor has symbols.
Under Settings → Public details, confirm your Statement descriptor matches what appears on customer credit card statements. This defaults to your Stripe account name but can be overridden per charge. Keep it under 22 characters or banks may truncate it and buyers could dispute the charge as fraud.
Webhook endpoints you'll need
Click Developers → Webhooks and add an endpoint for your production domain: https://yourdomain.com/webhooks/stripe. Select a recent API version. Subscribe to these events:
account.updatedaccount.external_account.createdcapability.updatedcharge.succeededcharge.refundedpayout.paidpayout.failed
Copy the Signing secret (starts with whsec_) and store it in your environment as STRIPE_WEBHOOK_SECRET. You'll use this to verify webhook signatures and prevent replay attacks.
Creating Express connected accounts programmatically
When a vendor completes signup on your platform, create a Stripe connected account before you let them list products. Store the Stripe account ID alongside your user record so you can look it up during checkout.
// Node.js with stripe npm package
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const account = await stripe.accounts.create({
type: 'express',
country: 'US',
email: '[email protected]',
capabilities: {
card_payments: { requested: true },
transfers: { requested: true },
},
business_type: 'company', // or 'individual'
settings: {
payouts: {
schedule: {
delay_days: 2,
interval: 'daily',
},
},
},
});
// Save account.id to your database
await db.users.update(vendorId, { stripeAccountId: account.id });
# Python with stripe library
import stripe
stripe.api_key = os.environ['STRIPE_SECRET_KEY']
account = stripe.Account.create(
type='express',
country='US',
email='[email protected]',
capabilities={
'card_payments': {'requested': True},
'transfers': {'requested': True},
},
business_type='company',
settings={
'payouts': {
'schedule': {
'delay_days': 2,
'interval': 'daily',
},
},
},
)
# Persist account.id in your user table
db.update_user(vendor_id, stripe_account_id=account.id)
Pass business_type: 'individual' for sole proprietors. Stripe will adjust the onboarding form to ask for SSN instead of EIN. If you serve international vendors, set country dynamically based on their profile. Stripe enables different capabilities and payout currencies per country, verify this in Stripe's country documentation before launch.
Building the onboarding flow and KYC collection
After you create the connected account, generate an AccountLink to send the vendor into Stripe's hosted onboarding. This link expires after a short period (typically 5 minutes), so generate it on-demand when the user clicks "Complete setup," not when they register.
Generating Account Links for Express onboarding
const accountLink = await stripe.accountLinks.create({
account: account.id,
refresh_url: 'https://yourdomain.com/vendor/onboarding/refresh',
return_url: 'https://yourdomain.com/vendor/onboarding/complete',
type: 'account_onboarding',
});
// Redirect the vendor to accountLink.url
res.redirect(accountLink.url);
The refresh_url is where Stripe redirects if the link expires while the vendor is filling out forms. Your handler should generate a fresh AccountLink with the same parameters and redirect again. The return_url is where Stripe sends the vendor after they submit. Don't treat arrival at return_url as proof of completion, Stripe posts a webhook when KYC actually clears.
Handling return URLs and refresh URLs
At your return_url endpoint, show a "We're reviewing your information" message and poll the account status. Many vendors close the tab before Stripe's backend finishes processing, so don't block your UI on the webhook.
app.get('/vendor/onboarding/complete', async (req, res) => {
const user = await db.users.findById(req.session.userId);
const account = await stripe.accounts.retrieve(user.stripeAccountId);
if (account.charges_enabled) {
res.redirect('/vendor/dashboard');
} else {
res.render('onboarding-pending', { details_submitted: account.details_submitted });
}
});
Check account.charges_enabled. If true, the vendor can receive payments immediately. If false but details_submitted is true, Stripe is running background checks and you should show an estimated timeline (usually within a business day or two). If details_submitted is false, they didn't finish the form, generate a new AccountLink and prompt them to continue.
Monitoring account status with webhooks
Subscribe to account.updated and capability.updated. When charges_enabled flips to true, send the vendor an email and update your database to allow product listings.
// Webhook handler
app.post('/webhooks/stripe', bodyParser.raw({ type: 'application/json' }), (req, res) => {
const sig = req.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(req.body, sig, process.env.STRIPE_WEBHOOK_SECRET);
} catch (err) {
return res.status(400).send(`Webhook Error: ${err.message}`);
}
if (event.type === 'account.updated') {
const account = event.data.object;
if (account.charges_enabled) {
db.users.updateByStripeAccountId(account.id, { canReceivePayments: true });
sendEmail(account.email, 'Your store is live');
}
}
res.json({ received: true });
});
In our experience, attempting to process payments before charges_enabled is true will fail with account_invalid errors. Always wait for the webhook confirmation.
Accepting payments and routing funds to connected accounts
When a buyer checks out, create a PaymentIntent on your platform account and declare the destination connected account. Stripe moves the money in one atomic transaction.
Creating PaymentIntents with destination charges
const paymentIntent = await stripe.paymentIntents.create({
amount: 100000, // $1,000.00 in cents
currency: 'usd',
payment_method_types: ['card'],
on_behalf_of: connectedAccountId,
transfer_data: {
destination: connectedAccountId,
},
application_fee_amount: 12000, // platform fee (e.g., $120)
});
// Return paymentIntent.client_secret to your frontend to confirm the payment
Set on_behalf_of to the connected account ID so the charge appears in their Dashboard and you satisfy card network requirements for platform commerce. The application_fee_amount is your cut, specified in cents. Stripe deposits this to your platform balance. The connected account receives amount - application_fee_amount minus Stripe's processing fee (typically 2.9% + $0.30 for US cards).
Setting application fees (your platform cut)
Calculate your fee server-side based on your commission model. A common approach is a percentage of the transaction total. If you charge a flat fee, subtract it in cents: application_fee_amount: 500 for a $5 fee on any order size.
You can split fees across multiple connected accounts by creating separate Transfer objects after the charge succeeds, but that adds complexity. Start with one vendor per transaction.
Handling failed charges and partial refunds
Wrap your PaymentIntent creation in try-catch. If the buyer's card declines, Stripe throws card_declined. If the connected account is not onboarded, you'll get account_invalid with a message like "The destination account must have at least one verified external account."
try {
const paymentIntent = await stripe.paymentIntents.create({ /* ... */ });
} catch (err) {
if (err.code === 'account_invalid') {
// Prompt vendor to complete onboarding
} else if (err.decline_code) {
// Show decline reason to buyer
}
}
For refunds, call stripe.refunds.create({ payment_intent: paymentIntent.id }). Stripe automatically reverses the application fee and debits the connected account's balance. If the vendor's balance is insufficient, Stripe debits your platform balance and you need to collect from the vendor separately, consider implementing a reserve or escrow mechanism to handle this scenario.
Managing payouts and transfer schedules
Stripe schedules payouts to connected accounts based on the settings.payouts.schedule you set at account creation. The default is typically daily with a short delay for fraud monitoring. Vendors see this in their Express Dashboard and can adjust it themselves if you enable that permission.
To manually trigger a payout (useful for high-value vendors who negotiate faster payment terms), use the Payouts API:
const payout = await stripe.payouts.create(
{
amount: 50000, // $500.00
currency: 'usd',
},
{
stripeAccount: connectedAccountId, // Pass as header
}
);
You can only trigger payouts if the connected account has a positive balance. Attempting a payout larger than the balance returns insufficient_funds. Monitor payout.failed webhooks and surface these errors in a vendor-facing dashboard with a link to Stripe's balance page.
For reconciliation, subscribe to payout.paid and log the payout.id, payout.arrival_date, and payout.amount in your ledger. Stripe's API returns a balance_transaction ID for every charge, fee, and payout, so you can trace dollars across your entire transaction history. Many teams export this regularly to their ERP system via Stripe Sigma queries.
Common mistakes that silently break production
These failure modes often don't throw exceptions and aren't obvious in logs, but they can significantly degrade onboarding completion and delay payouts.
Mistake 1: Not refreshing expired Account Links
Account Links expire after a short period for security reasons. If a vendor opens the link, gets distracted, then returns later, Stripe shows an error page. Generating the link once during user registration and emailing it often results in vendors clicking after expiration and assuming onboarding is broken.
The fix: generate the AccountLink on-demand when the user clicks "Complete setup" in your UI. Store a boolean onboarding_started in your database but regenerate the URL every time they hit that button. For users who abandon mid-form, your refresh_url handler should create a fresh link automatically:
app.get('/vendor/onboarding/refresh', async (req, res) => {
const user = await db.users.findById(req.session.userId);
const accountLink = await stripe.accountLinks.create({
account: user.stripeAccountId,
refresh_url: 'https://yourdomain.com/vendor/onboarding/refresh',
return_url: 'https://yourdomain.com/vendor/onboarding/complete',
type: 'account_onboarding',
});
res.redirect(accountLink.url);
});
Implementing on-demand link generation significantly reduces drop-off between link click and form submission.
Mistake 2: Ignoring account.updated webhooks for capabilities
Polling stripe.accounts.retrieve() frequently after onboarding to check if charges_enabled is true can hit API rate limits, especially during periods with many simultaneous onboardings. During peak times, Stripe's rate limiter may return 429 errors, preventing you from processing live payments.
The correct pattern is to rely entirely on the account.updated webhook. Stripe fires this event shortly after KYC approval, and you can handle it asynchronously without rate limits. Refactor to a webhook-only approach:
if (event.type === 'account.updated') {
const account = event.data.object;
await db.users.updateByStripeAccountId(account.id, {
chargesEnabled: account.charges_enabled,
payoutsEnabled: account.payouts_enabled,
lastUpdated: new Date(),
});
}
This also catches edge cases where Stripe disables an account due to a returned payout or negative balance. Polling periodically would miss these state changes for significant periods.
Testing in dev: using Stripe test mode and test account flows
Stripe gives every account a test mode with separate API keys (prefixed sk_test_). Create test connected accounts the same way you create live ones, call stripe.accounts.create() with your test key and you'll get a test account ID starting with acct_.
To simulate onboarding, generate an AccountLink and open it in an incognito window. Stripe pre-fills most fields in test mode so you can click through quickly. Use test SSN 000-00-0000 for individual accounts and test EIN 00-0000000 for companies. Any routing number beginning with 11 is a test bank account, 110000000 with account number 000123456789 works well.
For instant approval, Stripe marks test accounts as charges_enabled: true within seconds. To simulate rejection, use test data that triggers validation failures (check Stripe's testing documentation for specific values). The account.updated webhook will fire with requirements.currently_due listing missing fields.
Stripe's test clocks let you fast-forward time to test payout schedules. Create a test clock, assign it to your connected account, then advance it to see payouts that would normally take days. This significantly reduces testing time.
To simulate a declined charge, use test card 4000 0000 0000 0002 (generic decline) or 4000 0000 0000 9995 (insufficient funds). For successful payments, use 4242 4242 4242 4242 with any future expiration date and any 3-digit CVC.
Related guides
- How to Optimize Shopify Product Pages for AI Search in 2026
- How Agencies Can Charge $5K/mo for AI Search Optimization (The Playbook)
FAQ: How to set up Stripe Connect
What is how to set up stripe connect?
Setting up Stripe Connect means configuring your Stripe account to split payments between your platform and third-party sellers. You enable Connect in the Dashboard, choose an account type (Express, Standard, or Custom), create connected accounts via API for each vendor, and use destination charges or direct charges to route funds.
How does how to set up stripe connect work?
You create a parent platform account with Stripe, then create child connected accounts for each vendor. When a customer pays, Stripe processes the charge on your platform account, deducts your application fee, and transfers the remainder to the connected account's balance. Stripe handles payouts to each vendor's bank account on a schedule you configure.
Why is how to set up stripe connect important?
Marketplaces and platforms need compliant payment splitting to scale beyond a few vendors. Manual invoicing or asking vendors to set up individual merchant accounts adds significant onboarding friction and creates tax reporting complexity. Connect automates KYC, handles tax forms in the US, and supports many countries with local payout rails.
How long does it take to set up Stripe Connect for a marketplace?
Dashboard configuration takes minutes. Integrating the API and building onboarding flows typically takes several days for a backend engineer, assuming you use Express accounts and don't customize the UI. Add additional time for webhook handlers and reconciliation logic. Total implementation time varies by team and requirements.
Do connected accounts need their own Stripe accounts?
Express and Custom accounts do not require vendors to have existing Stripe accounts, you create the account for them via API and they complete onboarding through a Stripe-hosted flow. Standard accounts require the vendor to first register a standalone Stripe account, then connect it to your platform. Most marketplaces use Express to reduce friction.
What fees does Stripe charge on Connect transactions?
Stripe typically charges around 2.9% + $0.30 per successful card charge in the US, plus a small additional percentage for Connect platforms. Check current Stripe pricing documentation for exact rates in your region. You set your own application fee on top of Stripe's processing fees.
Can I change from Express to Standard accounts after launch?
No. Account type is set at creation and cannot be migrated. If you need to switch a vendor from Express to Standard, you must create a new Standard connected account, update your database mappings, and ask the vendor to re-onboard. This breaks historical transaction links, so choose carefully upfront.
Next: go live checklist
You now have the full integration path from dashboard config to production payments. Before you flip to live mode, verify your webhook endpoint returns 200 responses quickly (within a few seconds), confirm you're storing stripe_account_id in your user table with a unique index, and review Stripe's Connect guidelines. If you're processing significant monthly volume, you may need to submit your platform profile for review.
Frequently asked
- What is how to set up stripe connect?
- Setting up Stripe Connect means configuring your Stripe account to split payments between your platform and third-party sellers. You enable Connect in the Dashboard, choose an account type (Express, Standard, or Custom), create connected accounts via API for each vendor, and use destination charges or direct charges to route funds.
- How does how to set up stripe connect work?
- You create a parent platform account with Stripe, then create child connected accounts for each vendor. When a customer pays, Stripe processes the charge on your platform account, deducts your application fee, and transfers the remainder to the connected account's balance. Stripe handles payouts to each vendor's bank account on a schedule you configure.
- Why is how to set up stripe connect important?
- Marketplaces and platforms need compliant payment splitting to scale beyond a few vendors. Manual invoicing or asking vendors to set up individual merchant accounts adds significant onboarding friction and creates tax reporting complexity. Connect automates KYC, handles tax forms in the US, and supports many countries with local payout rails.
- How long does it take to set up Stripe Connect for a marketplace?
- Dashboard configuration takes minutes. Integrating the API and building onboarding flows typically takes several days for a backend engineer, assuming you use Express accounts and don't customize the UI. Add additional time for webhook handlers and reconciliation logic. Total implementation time varies by team and requirements.
- Do connected accounts need their own Stripe accounts?
- Express and Custom accounts do not require vendors to have existing Stripe accounts, you create the account for them via API and they complete onboarding through a Stripe-hosted flow. Standard accounts require the vendor to first register a standalone Stripe account, then connect it to your platform. Most marketplaces use Express to reduce friction.
- What fees does Stripe charge on Connect transactions?
- Stripe typically charges around 2.9% + $0.30 per successful card charge in the US, plus a small additional percentage for Connect platforms. Check current Stripe pricing documentation for exact rates in your region. You set your own application fee on top of Stripe's processing fees.
- Can I change from Express to Standard accounts after launch?