# How to use Stripe Connect for marketplace payments

- Tool: Stripe
- Difficulty: Advanced
- Time required: 30 minutes
- Compatibility: Stripe API v2024-12+, Node.js 18+
- Last updated: March 2026

## TL;DR

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.

## Before you start

- 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

### 1. 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.

### 2. Create a connected account

Use the API to create a connected account for each seller on your platform.

```
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

async function createConnectedAccount() {
  const account = await stripe.accounts.create({
    type: 'express',
    country: 'US',
    email: 'seller@example.com',
    capabilities: {
      card_payments: { requested: true },
      transfers: { requested: true },
    },
  });
  console.log('Connected account created:', account.id);
  return account;
}

createConnectedAccount();
```

**Expected result:** A new Express connected account is created with an ID like acct_1ABC123.

### 3. 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.

```
async function createOnboardingLink(accountId) {
  const accountLink = await stripe.accountLinks.create({
    account: accountId,
    refresh_url: 'https://yoursite.com/onboarding/refresh',
    return_url: 'https://yoursite.com/onboarding/complete',
    type: 'account_onboarding',
  });
  console.log('Onboarding URL:', accountLink.url);
  return accountLink.url;
}

createOnboardingLink('acct_1ABC123');
```

**Expected result:** A URL is returned that redirects the seller to Stripe's onboarding form.

### 4. 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.

```
async function createDestinationCharge(connectedAccountId) {
  const paymentIntent = await stripe.paymentIntents.create({
    amount: 10000, // $100.00 in cents
    currency: 'usd',
    payment_method_types: ['card'],
    application_fee_amount: 1500, // $15.00 platform fee
    transfer_data: {
      destination: connectedAccountId,
    },
  });
  console.log('PaymentIntent created:', paymentIntent.id);
  return paymentIntent;
}

createDestinationCharge('acct_1ABC123');
```

**Expected result:** A PaymentIntent is created. When confirmed, $85.00 goes to the seller and $15.00 to your platform.

### 5. 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.

```
async function chargeAndTransfer() {
  // Step 1: Charge the buyer on your platform
  const paymentIntent = await stripe.paymentIntents.create({
    amount: 20000, // $200.00
    currency: 'usd',
    payment_method_types: ['card'],
  });

  // Step 2: Transfer to each seller after payment succeeds
  const transfer1 = await stripe.transfers.create({
    amount: 8000, // $80.00 to seller A
    currency: 'usd',
    destination: 'acct_sellerA',
    source_transaction: paymentIntent.latest_charge,
  });

  const transfer2 = await stripe.transfers.create({
    amount: 10000, // $100.00 to seller B
    currency: 'usd',
    destination: 'acct_sellerB',
    source_transaction: paymentIntent.latest_charge,
  });

  console.log('Transfers created:', transfer1.id, transfer2.id);
  // Platform keeps $20.00
}

chargeAndTransfer();
```

**Expected result:** One charge to the buyer and two separate transfers to different sellers. Your platform retains the remainder.

## Complete code example

File: `marketplace-payments.js`

```javascript
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const express = require('express');
const app = express();

// Create a connected Express account for a new seller
app.post('/api/create-seller', async (req, res) => {
  try {
    const account = await stripe.accounts.create({
      type: 'express',
      country: req.body.country || 'US',
      email: req.body.email,
      capabilities: {
        card_payments: { requested: true },
        transfers: { requested: true },
      },
    });

    const accountLink = await stripe.accountLinks.create({
      account: account.id,
      refresh_url: `${req.headers.origin}/onboarding/refresh`,
      return_url: `${req.headers.origin}/onboarding/complete`,
      type: 'account_onboarding',
    });

    res.json({ accountId: account.id, onboardingUrl: accountLink.url });
  } catch (err) {
    res.status(400).json({ error: err.message });
  }
});

// Create a destination charge (single seller)
app.post('/api/charge', async (req, res) => {
  try {
    const { amount, sellerAccountId, platformFee } = req.body;
    const paymentIntent = await stripe.paymentIntents.create({
      amount: amount, // in cents
      currency: 'usd',
      payment_method_types: ['card'],
      application_fee_amount: platformFee,
      transfer_data: {
        destination: sellerAccountId,
      },
    });
    res.json({ clientSecret: paymentIntent.client_secret });
  } catch (err) {
    res.status(400).json({ error: err.message });
  }
});

// Check connected account status
app.get('/api/seller/:accountId', async (req, res) => {
  try {
    const account = await stripe.accounts.retrieve(req.params.accountId);
    res.json({
      payoutsEnabled: account.payouts_enabled,
      chargesEnabled: account.charges_enabled,
      requirements: account.requirements.currently_due,
    });
  } catch (err) {
    res.status(400).json({ error: err.message });
  }
});

// Webhook for Connect events
app.post('/webhook', express.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}`);
  }

  switch (event.type) {
    case 'account.updated':
      console.log('Account updated:', event.data.object.id);
      break;
    case 'payment_intent.succeeded':
      console.log('Payment succeeded:', event.data.object.id);
      break;
  }
  res.json({ received: true });
});

app.use(express.json());
app.listen(3000, () => console.log('Marketplace server on port 3000'));
```

## Common mistakes

- **Using direct charges when destination charges would be simpler** — undefined Fix: Destination charges are simpler for most marketplaces. Use direct charges only when the connected account should be the merchant of record.
- **Not checking charges_enabled before routing payments to a connected account** — undefined Fix: Always verify account.charges_enabled is true before creating charges on behalf of a connected account.
- **Hardcoding Account Link URLs instead of generating fresh ones** — undefined Fix: Account Links expire quickly. Always generate a new one when the seller initiates onboarding.
- **Forgetting to request capabilities when creating connected accounts** — undefined Fix: 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

## 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.

---

Source: https://www.rapidevelopers.com/stripe-guide/how-to-use-stripe-connect-for-marketplace-payments
© RapidDev — https://www.rapidevelopers.com/stripe-guide/how-to-use-stripe-connect-for-marketplace-payments
