# How to onboard sellers with Stripe Connect

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

## TL;DR

Seller onboarding in Stripe Connect involves creating a connected account, collecting identity and business information, and handling verification requirements. This guide covers the complete onboarding lifecycle including progressive onboarding, requirement deadlines, handling failures, and monitoring onboarding status with webhooks for all three account types.

## Complete Seller Onboarding Lifecycle with Stripe Connect

Seller onboarding is the most critical part of a marketplace integration. Stripe requires identity verification, business information, and banking details before a seller can accept payments. The onboarding requirements vary by country and business type. This guide covers progressive onboarding — letting sellers start accepting payments quickly while collecting remaining requirements over time.

## Before you start

- Stripe account with Connect enabled
- Node.js 18 or later installed
- Stripe Node.js SDK: npm install stripe
- Express.js: npm install express
- A database to store seller account IDs and onboarding status

## Step-by-step guide

### 1. Create a connected account with pre-filled data

Pre-fill as much seller information as possible when creating the account. This reduces friction during onboarding and speeds up verification.

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

async function createSellerAccount(sellerData) {
  const account = await stripe.accounts.create({
    type: 'express',
    country: sellerData.country,
    email: sellerData.email,
    capabilities: {
      card_payments: { requested: true },
      transfers: { requested: true },
    },
    business_type: 'individual',
    individual: {
      first_name: sellerData.firstName,
      last_name: sellerData.lastName,
      email: sellerData.email,
    },
    business_profile: {
      url: sellerData.websiteUrl,
      mcc: '5734', // Computer software stores
    },
    metadata: {
      platform_user_id: sellerData.userId,
    },
  });
  return account;
}
```

**Expected result:** A connected account is created with pre-filled data, reducing the number of fields the seller needs to complete.

### 2. Implement progressive onboarding

Stripe has two types of requirements: currently_due (needed now) and eventually_due (needed later). Progressive onboarding lets sellers start accepting payments after meeting currently_due requirements.

```
async function checkOnboardingStatus(accountId) {
  const account = await stripe.accounts.retrieve(accountId);

  const status = {
    chargesEnabled: account.charges_enabled,
    payoutsEnabled: account.payouts_enabled,
    currentlyDue: account.requirements.currently_due,
    eventuallyDue: account.requirements.eventually_due,
    pastDue: account.requirements.past_due,
    currentDeadline: account.requirements.current_deadline,
    errors: account.requirements.errors,
  };

  if (status.currentlyDue.length === 0 && status.chargesEnabled) {
    status.phase = 'complete';
  } else if (status.pastDue.length > 0) {
    status.phase = 'restricted'; // Action needed urgently
  } else if (status.currentlyDue.length > 0) {
    status.phase = 'in_progress';
  }

  return status;
}
```

**Expected result:** Returns a status object showing the onboarding phase and any outstanding requirements.

### 3. Handle requirement deadlines

Stripe sets deadlines for requirements. If not met, the account may be restricted. Monitor current_deadline and send reminders to sellers.

```
async function checkDeadlines(accountId) {
  const account = await stripe.accounts.retrieve(accountId);
  const deadline = account.requirements.current_deadline;

  if (deadline) {
    const deadlineDate = new Date(deadline * 1000);
    const daysLeft = Math.ceil((deadlineDate - Date.now()) / (1000 * 60 * 60 * 24));

    console.log(`Deadline: ${deadlineDate.toISOString()}`);
    console.log(`Days remaining: ${daysLeft}`);
    console.log('Requirements due:', account.requirements.currently_due);

    if (daysLeft <= 7) {
      // Send urgent reminder to seller
      console.log('URGENT: Send reminder to seller!');
    }
  } else {
    console.log('No active deadline.');
  }
}
```

**Expected result:** Console shows the deadline date, days remaining, and outstanding requirements.

### 4. Handle verification failures

When Stripe rejects a verification document or finds issues, the requirements.errors array contains the specific reasons. Generate a new Account Link so the seller can resubmit.

```
async function handleVerificationFailure(accountId) {
  const account = await stripe.accounts.retrieve(accountId);
  const errors = account.requirements.errors;

  if (errors && errors.length > 0) {
    errors.forEach((error) => {
      console.log(`Requirement: ${error.requirement}`);
      console.log(`Reason: ${error.reason}`);
      console.log(`Code: ${error.code}`);
    });

    // Generate a new onboarding link for the seller to fix issues
    const accountLink = await stripe.accountLinks.create({
      account: accountId,
      refresh_url: `https://yourplatform.com/onboarding/refresh?acct=${accountId}`,
      return_url: `https://yourplatform.com/onboarding/complete?acct=${accountId}`,
      type: 'account_onboarding',
    });

    return { errors, fixUrl: accountLink.url };
  }

  return { errors: [], fixUrl: null };
}
```

**Expected result:** Returns verification errors with human-readable reasons and a URL for the seller to resubmit.

### 5. Set up webhook monitoring for onboarding events

The account.updated webhook fires whenever a connected account's status changes. Use it to track onboarding progress and notify sellers.

```
const express = require('express');
const app = express();

app.post('/webhook', express.raw({ type: 'application/json' }), async (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;
    const accountId = account.id;

    if (account.charges_enabled && account.payouts_enabled) {
      console.log(`${accountId}: Fully onboarded`);
      // Update DB, notify seller
    } else if (account.requirements.past_due.length > 0) {
      console.log(`${accountId}: Has overdue requirements`);
      // Send urgent notification
    } else if (account.requirements.errors.length > 0) {
      console.log(`${accountId}: Verification failed`);
      // Notify seller to resubmit
    }
  }

  res.json({ received: true });
});
```

**Expected result:** Webhook handler processes account updates and takes appropriate action based on the onboarding state.

## Complete code example

File: `seller-onboarding.js`

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

app.use(express.json());

app.post('/api/sellers/onboard', async (req, res) => {
  try {
    const { email, firstName, lastName, country } = req.body;
    const account = await stripe.accounts.create({
      type: 'express',
      country: country || 'US',
      email,
      capabilities: { card_payments: { requested: true }, transfers: { requested: true } },
      individual: { first_name: firstName, last_name: lastName, email },
    });

    const link = await stripe.accountLinks.create({
      account: account.id,
      refresh_url: `https://yourplatform.com/onboarding/refresh?acct=${account.id}`,
      return_url: `https://yourplatform.com/onboarding/complete?acct=${account.id}`,
      type: 'account_onboarding',
    });

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

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

app.post('/api/sellers/:accountId/fix', async (req, res) => {
  try {
    const link = await stripe.accountLinks.create({
      account: req.params.accountId,
      refresh_url: `https://yourplatform.com/onboarding/refresh?acct=${req.params.accountId}`,
      return_url: `https://yourplatform.com/onboarding/complete?acct=${req.params.accountId}`,
      type: 'account_onboarding',
    });
    res.json({ url: link.url });
  } catch (err) {
    res.status(400).json({ error: err.message });
  }
});

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}`);
  }
  if (event.type === 'account.updated') {
    const acct = event.data.object;
    console.log(`${acct.id}: charges=${acct.charges_enabled} payouts=${acct.payouts_enabled}`);
    console.log('  Due:', acct.requirements.currently_due);
  }
  res.json({ received: true });
});

app.listen(3000, () => console.log('Onboarding server on port 3000'));
```

## Common mistakes

- **Not monitoring requirements.eventually_due** — undefined Fix: Eventually_due requirements become currently_due over time. Track them proactively to avoid account restrictions.
- **Ignoring current_deadline on requirements** — undefined Fix: If requirements are not met by the deadline, Stripe may disable charges or payouts. Send reminders before deadlines.
- **Not handling verification errors gracefully** — undefined Fix: When verification fails, show the seller a clear message about what went wrong and provide a link to resubmit.
- **Relying only on return_url to determine onboarding completion** — undefined Fix: The return URL just means the seller left the form. Use webhooks and charges_enabled as the source of truth.

## Best practices

- Pre-fill as much seller data as possible to reduce onboarding drop-off
- Use account.updated webhooks as the authoritative source for onboarding status
- Send reminder emails to sellers with upcoming requirement deadlines
- Store requirement errors in your database for support troubleshooting
- Implement a seller dashboard that shows their onboarding progress and any action items
- Test all onboarding scenarios in test mode including verification failures
- For complex multi-seller onboarding, consider working with RapidDev to build a robust onboarding pipeline

## Frequently asked questions

### What is progressive onboarding in Stripe Connect?

Progressive onboarding lets sellers start accepting payments after meeting a minimum set of requirements (currently_due). Additional requirements (eventually_due) are collected over time, with deadlines set by Stripe.

### How do I know which requirements a seller must complete?

Use the accounts.retrieve API to check requirements.currently_due for immediate needs and requirements.eventually_due for future needs. The exact requirements depend on country, business type, and capabilities.

### What happens if a seller misses a requirement deadline?

Stripe may disable charges, payouts, or both on the account. The seller must complete the overdue requirements before functionality is restored.

### Can I collect verification documents myself instead of using Account Links?

Yes, with Custom accounts you can collect documents via your own UI and upload them via the API. With Express accounts, Stripe's hosted onboarding handles document collection.

### How long does Stripe take to verify seller documents?

Automated checks (SSN, address) complete in seconds to minutes. Manual document reviews (ID, proof of address) typically take 1-3 business days.

---

Source: https://www.rapidevelopers.com/stripe-guide/how-to-onboard-sellers-with-stripe-connect
© RapidDev — https://www.rapidevelopers.com/stripe-guide/how-to-onboard-sellers-with-stripe-connect
