Back to Blog

Stripe Connect for Marketplaces: Split Payments, Onboarding, and Payouts

Build a Stripe Connect marketplace with split payments, seller onboarding, platform fees, and automated payouts. Complete TypeScript implementation with webhook

Viprasol Tech Team
March 24, 2026
13 min read

Stripe Connect for Marketplaces: Split Payments, Onboarding, and Payouts

Marketplaces โ€” platforms where buyers pay sellers through your product โ€” have non-trivial payment infrastructure requirements. You need to collect money from buyers, split it between sellers and yourself, handle failed payouts, manage compliance across jurisdictions, and onboard sellers without requiring them to have existing payment accounts.

Stripe Connect is the most complete managed solution for this. This guide covers the complete implementation: account types, onboarding flows, payment splitting, webhook handling, and the failure cases most tutorials skip.


Stripe Connect Account Types

Three account types exist, each with different tradeoffs:

TypeUX ControlKYC ResponsibilityStripe Branding VisibleBest For
StandardSeller uses Stripe dashboardStripe handlesYesPlatforms where sellers already use Stripe
ExpressStripe-hosted onboardingStripe handlesMinimalMost marketplaces โ€” fastest to ship
CustomFully white-labeledPlatform responsibleNoEnterprise, financial services

Recommendation for most marketplaces: Express. You get Stripe-hosted KYC/KYB onboarding (they handle identity verification, bank account collection, and compliance), your platform brand is visible, and sellers use a simplified Stripe Express dashboard rather than the full Stripe interface.


Step 1: Create a Connected Account and Onboarding Link

// lib/stripe.ts
import Stripe from 'stripe';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: '2024-12-18',
  typescript: true,
});

export { stripe };

// api/sellers/onboard.ts โ€” Create Express account + onboarding link
export async function POST(request: Request) {
  const { sellerId, email, businessType } = await request.json();

  // 1. Check if seller already has a Stripe account
  const seller = await db.seller.findUnique({ where: { id: sellerId } });
  
  let stripeAccountId = seller?.stripeAccountId;

  if (!stripeAccountId) {
    // 2. Create Express connected account
    const account = await stripe.accounts.create({
      type: 'express',
      email,
      business_type: businessType ?? 'individual', // 'individual' | 'company'
      capabilities: {
        card_payments: { requested: true },
        transfers: { requested: true },
      },
      settings: {
        payouts: {
          schedule: {
            interval: 'weekly',  // 'daily' | 'weekly' | 'monthly'
            weekly_anchor: 'monday',
          },
        },
      },
      metadata: { sellerId },
    });

    stripeAccountId = account.id;

    // 3. Save to DB
    await db.seller.update({
      where: { id: sellerId },
      data: {
        stripeAccountId,
        stripeOnboardingStatus: 'pending',
      },
    });
  }

  // 4. Generate onboarding link (expires after ~1 hour)
  const accountLink = await stripe.accountLinks.create({
    account: stripeAccountId,
    refresh_url: `${process.env.APP_URL}/sellers/onboard/refresh`,
    return_url: `${process.env.APP_URL}/sellers/onboard/complete`,
    type: 'account_onboarding',
  });

  return Response.json({ url: accountLink.url });
}

The seller clicks the link, completes Stripe's hosted flow (identity, bank account), and lands on your return_url. Check their account status there:

// api/sellers/onboard/complete.ts
export async function GET(request: Request) {
  const { searchParams } = new URL(request.url);
  const sellerId = searchParams.get('sellerId')!;

  const seller = await db.seller.findUnique({ where: { id: sellerId } });
  const account = await stripe.accounts.retrieve(seller!.stripeAccountId!);

  const isEnabled = 
    account.charges_enabled && 
    account.payouts_enabled && 
    account.details_submitted;

  await db.seller.update({
    where: { id: sellerId },
    data: {
      stripeOnboardingStatus: isEnabled ? 'complete' : 'pending',
      stripeChargesEnabled: account.charges_enabled,
      stripePayoutsEnabled: account.payouts_enabled,
    },
  });

  return Response.redirect(
    isEnabled
      ? `${process.env.APP_URL}/sellers/dashboard`
      : `${process.env.APP_URL}/sellers/onboard/incomplete`
  );
}

๐Ÿค– Can This Strategy Be Automated?

In 2026, top traders run custom EAs โ€” not manual charts. We build MT4/MT5 Expert Advisors that execute your exact strategy 24/7, pass prop firm challenges, and eliminate emotional decisions.

  • Runs 24/7 โ€” no screen time, no missed entries
  • Prop-firm compliant (FTMO, MFF, TFT drawdown rules)
  • MyFXBook-verified backtest results included
  • From strategy brief to live EA in 2โ€“4 weeks

Step 2: Collecting Payment with Automatic Split

When a buyer pays, create a PaymentIntent that automatically routes the seller's share to their account:

// api/orders/create.ts
interface CreateOrderBody {
  buyerId: string;
  sellerId: string;
  items: Array<{ productId: string; quantity: number; unitPrice: number }>;
}

export async function POST(request: Request) {
  const body: CreateOrderBody = await request.json();

  const seller = await db.seller.findUnique({ where: { id: body.sellerId } });

  if (!seller?.stripeAccountId || !seller.stripeChargesEnabled) {
    return Response.json({ error: 'Seller not ready to accept payments' }, { status: 400 });
  }

  // Calculate amounts (in cents)
  const subtotalCents = body.items.reduce(
    (sum, item) => sum + item.unitPrice * item.quantity,
    0
  );
  const platformFeeCents = Math.round(subtotalCents * 0.05); // 5% platform fee
  const sellerShareCents = subtotalCents - platformFeeCents;

  // Create order record
  const order = await db.order.create({
    data: {
      buyerId: body.buyerId,
      sellerId: body.sellerId,
      subtotalCents,
      platformFeeCents,
      sellerShareCents,
      status: 'PENDING_PAYMENT',
      items: { create: body.items },
    },
  });

  // Create PaymentIntent with destination charge
  const paymentIntent = await stripe.paymentIntents.create({
    amount: subtotalCents,
    currency: 'usd',
    payment_method_types: ['card'],
    application_fee_amount: platformFeeCents,  // Platform keeps this
    transfer_data: {
      destination: seller.stripeAccountId,     // Seller receives the rest
    },
    metadata: {
      orderId: order.id,
      buyerId: body.buyerId,
      sellerId: body.sellerId,
    },
    description: `Order ${order.id} โ€” ${body.items.length} item(s)`,
  });

  // Save PaymentIntent ID
  await db.order.update({
    where: { id: order.id },
    data: { stripePaymentIntentId: paymentIntent.id },
  });

  return Response.json({
    clientSecret: paymentIntent.client_secret,
    orderId: order.id,
  });
}

Step 3: Handling Webhooks

Webhooks are how Stripe tells you when payments succeed, fail, or sellers update their accounts.

// api/webhooks/stripe.ts
import { stripe } from '@/lib/stripe';

export async function POST(request: Request) {
  const rawBody = await request.arrayBuffer();
  const sig = request.headers.get('stripe-signature')!;

  let event: Stripe.Event;
  try {
    event = stripe.webhooks.constructEvent(
      Buffer.from(rawBody),
      sig,
      process.env.STRIPE_WEBHOOK_SECRET!
    );
  } catch (err) {
    console.error('Webhook signature verification failed:', err);
    return Response.json({ error: 'Invalid signature' }, { status: 400 });
  }

  // Idempotency โ€” skip if already processed
  const existing = await db.processedStripeEvent.findUnique({
    where: { stripeEventId: event.id },
  });
  if (existing) return Response.json({ received: true });

  await db.processedStripeEvent.create({
    data: { stripeEventId: event.id, type: event.type },
  });

  switch (event.type) {
    case 'payment_intent.succeeded': {
      const pi = event.data.object as Stripe.PaymentIntent;
      await db.order.update({
        where: { stripePaymentIntentId: pi.id },
        data: { status: 'PAID', paidAt: new Date() },
      });
      await notifySellerOfNewOrder(pi.metadata.orderId);
      break;
    }

    case 'payment_intent.payment_failed': {
      const pi = event.data.object as Stripe.PaymentIntent;
      await db.order.update({
        where: { stripePaymentIntentId: pi.id },
        data: {
          status: 'PAYMENT_FAILED',
          failureReason: pi.last_payment_error?.message,
        },
      });
      break;
    }

    case 'account.updated': {
      // Seller updated their Stripe account (completed onboarding, added bank, etc.)
      const account = event.data.object as Stripe.Account;
      const seller = await db.seller.findFirst({
        where: { stripeAccountId: account.id },
      });
      if (seller) {
        await db.seller.update({
          where: { id: seller.id },
          data: {
            stripeChargesEnabled: account.charges_enabled,
            stripePayoutsEnabled: account.payouts_enabled,
            stripeOnboardingStatus:
              account.charges_enabled && account.payouts_enabled
                ? 'complete'
                : 'pending',
          },
        });
      }
      break;
    }

    case 'payout.paid': {
      const payout = event.data.object as Stripe.Payout;
      // Log successful payout to seller's bank
      await db.sellerPayout.updateMany({
        where: { stripePayoutId: payout.id },
        data: { status: 'PAID', paidAt: new Date() },
      });
      break;
    }

    case 'payout.failed': {
      const payout = event.data.object as Stripe.Payout;
      await handleFailedPayout(payout);  // Alert seller, update status
      break;
    }
  }

  return Response.json({ received: true });
}

๐Ÿ“ˆ Stop Trading Manually โ€” Let AI Do It

While you sleep, your EA keeps working. Viprasol builds prop-firm-compliant Expert Advisors with strict risk management, real backtests, and live deployment support.

  • No rule violations โ€” daily drawdown, max drawdown, consistency rules built in
  • Covers MT4, MT5, cTrader, and Python-based algos
  • 5.0โ˜… Upwork record โ€” 100% job success rate
  • Free strategy consultation before we write a single line

Step 4: Handling Refunds

Refunds on marketplace payments require coordination between platform fee reversal and seller balance:

// api/orders/refund.ts
export async function POST(request: Request) {
  const { orderId, reason } = await request.json();

  const order = await db.order.findUnique({
    where: { id: orderId },
    include: { seller: true },
  });

  if (!order?.stripePaymentIntentId) {
    return Response.json({ error: 'Order not found or not paid' }, { status: 400 });
  }

  // Full refund โ€” reverses both platform fee and seller transfer
  const refund = await stripe.refunds.create({
    payment_intent: order.stripePaymentIntentId,
    reason: reason ?? 'requested_by_customer',
    refund_application_fee: true,  // Refund the platform fee too
    reverse_transfer: true,         // Pull back money from seller's balance
  });

  await db.order.update({
    where: { id: orderId },
    data: {
      status: 'REFUNDED',
      stripeRefundId: refund.id,
      refundedAt: new Date(),
    },
  });

  return Response.json({ refundId: refund.id, status: refund.status });
}

Compliance and Risk Considerations

AreaWhat You're Responsible For
KYC/KYBExpress: Stripe handles. Custom: You must implement.
1099-K reportingStripe files for sellers who exceed IRS thresholds ($600+ in 2026)
Chargeback liabilityDestination charges: seller bears chargeback risk, not platform
Restricted businessesYou must not onboard sellers in prohibited categories (gambling, adult content in some regions, etc.)
International payoutsStripe handles currency conversion; you set payout currency per account
Funds holdingStripe holds funds 7 days by default for new accounts before releasing to sellers

Platform Fee Strategies

ModelImplementationBest For
Flat percentageapplication_fee_amount = amount * 0.05Simplest, easiest to explain
Tiered by volumeFetch seller's MTD volume, apply tier rateIncentivizes high-volume sellers
Per-transaction + %fee = 50 + amount * 0.025Covers Stripe's base cost
Category-basedDifferent rates per product categoryMarketplaces with variable-margin categories

Cost Model

ComponentCost
Stripe processing2.9% + $0.30 per transaction
Stripe Connect (Express)0.25% per payout (max $2/payout)
Instant payouts (optional)1% per instant payout
Stripe Radar (fraud)$0.05/transaction (paid plan)
International cards+1.5%

Platform economics example (5% platform fee, $100 order):

  • Buyer pays: $100.00
  • Stripe fee: $3.20 (2.9% + $0.30)
  • Platform fee: $5.00
  • Stripe Connect fee: $0.25
  • Net to platform: $1.55
  • Net to seller: $91.25

Build vs Buy

ApproachTimelineCostMaintenance
Stripe Connect Express (this guide)4โ€“8 weeks$15,000โ€“40,000 devLow
Stripe Connect Custom8โ€“16 weeks$40,000โ€“100,000 devMedium
Full custom (own bank relationships)6โ€“18 months$200,000โ€“500,000+Very High

For 99% of marketplaces, Stripe Connect Express is the right choice.


Working With Viprasol

We've built marketplace payment infrastructure for multi-vendor e-commerce platforms, service marketplaces, and gig economy apps. Our implementations include seller onboarding, split payment flows, payout dashboards, dispute handling, and regulatory compliance tooling.

โ†’ Talk to our payments team about your marketplace architecture.


See Also

Share this article:

About the Author

V

Viprasol Tech Team

Custom Software Development Specialists

The Viprasol Tech team specialises in algorithmic trading software, AI agent systems, and SaaS development. With 100+ projects delivered across MT4/MT5 EAs, fintech platforms, and production AI systems, the team brings deep technical experience to every engagement. Based in India, serving clients globally.

MT4/MT5 EA DevelopmentAI Agent SystemsSaaS DevelopmentAlgorithmic Trading

Ready to Automate Your Trading?

Get a custom Expert Advisor built by professionals with verified MyFXBook results.

Free consultation โ€ข No commitment โ€ข Response within 24 hours

Viprasol ยท Trading Software

Need a custom EA or trading bot built?

We specialise in MT4/MT5 Expert Advisor development โ€” prop-firm compliant, forward-tested before live, MyFXBook verifiable. 5.0โ˜… Upwork, 100% Job Success, 100+ projects shipped.