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
Create the Express connected account
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.
1const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);23async 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 user15 },16 });17 console.log('Account created:', account.id);18 return account;19}2021createExpressAccount('seller@example.com', 'US');Expected result: A new Express connected account is created with an ID like acct_1N...
Generate the onboarding Account Link
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.
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}1112createAccountLink('acct_1N...');Expected result: A URL is returned. Redirecting the seller to this URL shows Stripe's onboarding form.
Handle the return URL
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.
1const express = require('express');2const app = express();34app.get('/onboarding/complete', async (req, res) => {5 const accountId = req.query.account_id; // You'd store this in session6 const account = await stripe.accounts.retrieve(accountId);78 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 pending12 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});2122app.get('/onboarding/refresh', async (req, res) => {23 // Account Link expired, generate a new one24 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.
Listen for account.updated webhooks
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.
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_SECRET9 );10 } catch (err) {11 return res.status(400).send(`Webhook Error: ${err.message}`);12 }1314 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 active19 }20 }2122 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
1const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);2const express = require('express');3const app = express();45// Store account IDs in your database — this is a simplified in-memory map6const sellerAccounts = new Map();78app.use(express.json());910app.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 });2223 sellerAccounts.set(req.body.email, account.id);2425 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 });3132 res.json({ accountId: account.id, url: accountLink.url });33 } catch (err) {34 res.status(400).json({ error: err.message });35 }36});3738app.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});5253app.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});6263app.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_SECRET69 );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});7980app.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.
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.
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).
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation