Skip to main content
RapidDev - Software Development Agency
stripe-guide

How to create an Express account with Stripe Connect

Stripe Connect Express accounts let you onboard sellers with minimal code using Stripe-hosted forms. You create the account via the API, generate an Account Link for onboarding, and redirect the seller. Stripe handles identity verification, bank account collection, and compliance. This guide covers the full flow from account creation to confirming the seller is ready to accept payments.

What you'll learn

  • How to create an Express connected account via the Stripe API
  • How to generate Account Links for Stripe-hosted onboarding
  • How to handle the return and refresh URLs in your app
  • How to verify the account is fully onboarded and ready for charges
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Advanced6 min read20 minutesStripe API v2024-12+, Node.js 18+March 2026RapidDev Engineering Team
TL;DR

Stripe Connect Express accounts let you onboard sellers with minimal code using Stripe-hosted forms. You create the account via the API, generate an Account Link for onboarding, and redirect the seller. Stripe handles identity verification, bank account collection, and compliance. This guide covers the full flow from account creation to confirming the seller is ready to accept payments.

Express Account Onboarding with Stripe Connect

Express accounts are the most popular Connect account type because Stripe handles the onboarding UI, identity verification, and compliance requirements. Your platform creates the account, generates an onboarding link, and redirects the seller. After onboarding, the seller gets access to a simplified Express Dashboard to view payouts and manage their account.

Prerequisites

  • A Stripe account with Connect enabled
  • Node.js 18 or later installed
  • Stripe Node.js SDK: npm install stripe
  • Express.js for the server: npm install express

Step-by-step guide

1

Create the Express connected account

Create a new Express account for your seller. You can pre-fill information like email and country to streamline onboarding.

typescript
1const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
2
3async function createExpressAccount(email, country) {
4 const account = await stripe.accounts.create({
5 type: 'express',
6 country: country || 'US',
7 email: email,
8 capabilities: {
9 card_payments: { requested: true },
10 transfers: { requested: true },
11 },
12 business_type: 'individual',
13 metadata: {
14 platform_user_id: 'user_123', // Link to your internal user
15 },
16 });
17 console.log('Account created:', account.id);
18 return account;
19}
20
21createExpressAccount('seller@example.com', 'US');

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

2

Generate the onboarding Account Link

Account Links are short-lived URLs that redirect the seller to Stripe's hosted onboarding. Generate a new one each time the seller starts or resumes onboarding.

typescript
1async function createAccountLink(accountId) {
2 const accountLink = await stripe.accountLinks.create({
3 account: accountId,
4 refresh_url: 'https://yourplatform.com/onboarding/refresh',
5 return_url: 'https://yourplatform.com/onboarding/complete',
6 type: 'account_onboarding',
7 });
8 console.log('Redirect seller to:', accountLink.url);
9 return accountLink;
10}
11
12createAccountLink('acct_1N...');

Expected result: A URL is returned. Redirecting the seller to this URL shows Stripe's onboarding form.

3

Handle the return URL

When the seller completes onboarding (or leaves midway), Stripe redirects them to your return_url. Check the account status to determine if onboarding is complete.

typescript
1const express = require('express');
2const app = express();
3
4app.get('/onboarding/complete', async (req, res) => {
5 const accountId = req.query.account_id; // You'd store this in session
6 const account = await stripe.accounts.retrieve(accountId);
7
8 if (account.charges_enabled && account.payouts_enabled) {
9 res.send('Onboarding complete! You can now accept payments.');
10 } else {
11 // Seller left early or requirements still pending
12 const link = await stripe.accountLinks.create({
13 account: accountId,
14 refresh_url: 'https://yourplatform.com/onboarding/refresh',
15 return_url: 'https://yourplatform.com/onboarding/complete',
16 type: 'account_onboarding',
17 });
18 res.redirect(link.url);
19 }
20});
21
22app.get('/onboarding/refresh', async (req, res) => {
23 // Account Link expired, generate a new one
24 const accountId = req.query.account_id;
25 const link = await stripe.accountLinks.create({
26 account: accountId,
27 refresh_url: 'https://yourplatform.com/onboarding/refresh',
28 return_url: 'https://yourplatform.com/onboarding/complete',
29 type: 'account_onboarding',
30 });
31 res.redirect(link.url);
32});

Expected result: Sellers who complete onboarding see a success message. Sellers who leave early are redirected back to continue.

4

Listen for account.updated webhooks

Onboarding completion is asynchronous — Stripe verifies documents in the background. Use webhooks to get notified when the account is fully enabled.

typescript
1app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
2 const sig = req.headers['stripe-signature'];
3 let event;
4 try {
5 event = stripe.webhooks.constructEvent(
6 req.body,
7 sig,
8 process.env.STRIPE_WEBHOOK_SECRET
9 );
10 } catch (err) {
11 return res.status(400).send(`Webhook Error: ${err.message}`);
12 }
13
14 if (event.type === 'account.updated') {
15 const account = event.data.object;
16 if (account.charges_enabled && account.payouts_enabled) {
17 console.log(`Account ${account.id} fully onboarded!`);
18 // Update your database: mark seller as active
19 }
20 }
21
22 res.json({ received: true });
23});

Expected result: Your server logs when a connected account becomes fully onboarded and ready to accept payments.

Complete working example

express-account-onboarding.js
1const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
2const express = require('express');
3const app = express();
4
5// Store account IDs in your database — this is a simplified in-memory map
6const sellerAccounts = new Map();
7
8app.use(express.json());
9
10app.post('/api/onboard-seller', async (req, res) => {
11 try {
12 const account = await stripe.accounts.create({
13 type: 'express',
14 country: req.body.country || 'US',
15 email: req.body.email,
16 capabilities: {
17 card_payments: { requested: true },
18 transfers: { requested: true },
19 },
20 business_type: 'individual',
21 });
22
23 sellerAccounts.set(req.body.email, account.id);
24
25 const accountLink = await stripe.accountLinks.create({
26 account: account.id,
27 refresh_url: `${req.headers.origin}/onboarding/refresh?acct=${account.id}`,
28 return_url: `${req.headers.origin}/onboarding/complete?acct=${account.id}`,
29 type: 'account_onboarding',
30 });
31
32 res.json({ accountId: account.id, url: accountLink.url });
33 } catch (err) {
34 res.status(400).json({ error: err.message });
35 }
36});
37
38app.get('/onboarding/complete', async (req, res) => {
39 const account = await stripe.accounts.retrieve(req.query.acct);
40 if (account.charges_enabled) {
41 res.send('Onboarding complete! You are ready to accept payments.');
42 } else {
43 const link = await stripe.accountLinks.create({
44 account: req.query.acct,
45 refresh_url: `https://yourplatform.com/onboarding/refresh?acct=${req.query.acct}`,
46 return_url: `https://yourplatform.com/onboarding/complete?acct=${req.query.acct}`,
47 type: 'account_onboarding',
48 });
49 res.redirect(link.url);
50 }
51});
52
53app.get('/onboarding/refresh', async (req, res) => {
54 const link = await stripe.accountLinks.create({
55 account: req.query.acct,
56 refresh_url: `https://yourplatform.com/onboarding/refresh?acct=${req.query.acct}`,
57 return_url: `https://yourplatform.com/onboarding/complete?acct=${req.query.acct}`,
58 type: 'account_onboarding',
59 });
60 res.redirect(link.url);
61});
62
63app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
64 const sig = req.headers['stripe-signature'];
65 let event;
66 try {
67 event = stripe.webhooks.constructEvent(
68 req.body, sig, process.env.STRIPE_WEBHOOK_SECRET
69 );
70 } catch (err) {
71 return res.status(400).send(`Webhook Error: ${err.message}`);
72 }
73 if (event.type === 'account.updated') {
74 const acct = event.data.object;
75 console.log(`Account ${acct.id}: charges=${acct.charges_enabled} payouts=${acct.payouts_enabled}`);
76 }
77 res.json({ received: true });
78});
79
80app.listen(3000, () => console.log('Server on port 3000'));

Common mistakes when creating an Express account with Stripe Connect

Why it's a problem: Caching Account Link URLs instead of generating new ones each time

How to avoid: Account Links expire quickly. Always create a fresh link when the seller clicks your onboarding button.

Why it's a problem: Assuming the seller is fully onboarded when they return to your return_url

How to avoid: The return_url only means the seller left the onboarding flow. Always check charges_enabled and payouts_enabled to confirm.

Why it's a problem: Not handling the refresh_url

How to avoid: If the Account Link expires, Stripe redirects to refresh_url. Generate a new link and redirect the seller back to continue.

Why it's a problem: Forgetting to request capabilities

How to avoid: Without card_payments and transfers capabilities, the account cannot process payments or receive funds.

Best practices

  • Store the connected account ID in your database immediately after creation, linked to your platform user
  • Always generate fresh Account Links — never cache or reuse them
  • Use webhooks (account.updated) as the source of truth for onboarding status, not the return URL
  • Pre-fill email and country to reduce onboarding friction
  • Add metadata to connected accounts to link them to your internal user IDs
  • Test the full onboarding flow in test mode before enabling live mode
  • Show a clear onboarding progress indicator in your UI

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

I need to create Stripe Connect Express accounts for sellers on my marketplace. Show me the complete Node.js code for account creation, onboarding links, return/refresh handling, and webhook verification.

Stripe Prompt

Help me build a seller onboarding flow using Stripe Connect Express accounts in my Node.js app. I need account creation, Account Link generation, return URL handling, and webhooks to confirm onboarding is complete.

Frequently asked questions

How long does Express account onboarding take?

The seller can complete the onboarding form in 5-10 minutes. Stripe's verification usually takes seconds for automated checks, but document review can take 1-3 business days.

Can I customize the Express onboarding form?

You can customize the brand color, icon, and name shown on the onboarding form via Dashboard → Connect → Settings → Branding. The form layout itself is controlled by Stripe.

What happens if a seller abandons onboarding midway?

The account remains in a partially onboarded state. You can generate a new Account Link to let them resume. Their progress is saved.

Can I pre-fill more than email and country?

Yes. You can pre-fill business_type, individual details (name, DOB, address), and business_profile (URL, MCC) when creating the account to skip those steps in onboarding.

Is there a cost for Express accounts?

Creating Express accounts is free. Stripe charges standard processing fees on transactions plus an additional fee per payout to connected accounts (varies by country).

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.