Stripe Connect onboarding is the process by which a seller, provider or business on your platform gives Stripe the information it needs to verify them and enable the capabilities they need, such as accepting card payments or receiving transfers and payouts. You choose one of three onboarding options (Stripe-hosted, embedded or API onboarding), send the seller through it, and then treat the connected account's `requirements` and the `account.updated` webhook as the source of truth for whether that seller can be paid.
The part most teams underestimate is that onboarding never really finishes. Requirements can appear later as a seller's volume grows, when regulations change or when a verification fails. This guide covers the whole loop: choosing an option, building the Account Link flow with its `return_url` and `refresh_url`, reading requirements and capabilities, designing a seller status UX, handling re-onboarding and paused payouts, mobile considerations, testing, and a checklist. Behaviors are checked against Stripe's Connect onboarding documentation as of October 2026.
What does Stripe Connect onboarding actually do?
A connected account is the Stripe representation of a seller or provider on your platform. Before Stripe enables charges and payouts for it, Stripe needs know-your-customer (KYC) and know-your-business information. What it needs varies with the account's country, its service agreement, the capabilities you request for it (for example `card_payments` or `transfers`) and its business type and structure. A capability is a permission to use a particular Stripe feature; each one comes with its own requirements.
Onboarding is how those requirements get collected. The outputs that matter to your code are on the Account object: `details_submitted` (has the seller finished the flow), `charges_enabled` and `payouts_enabled`, the status of each capability, and the `requirements` hash, which lists what is still needed and by when. Your platform's job is to send sellers through onboarding at the right moment, read those fields reliably and turn them into something a seller can understand.
How much of that job is yours depends on the account configuration you chose. If Stripe is responsible for requirement collection, Stripe's own onboarding UIs know what to ask for. If your platform is responsible (`controller.requirement_collection` set to `application`), you carry more of the collection and remediation work. The charge model you picked also matters, because it determines which capabilities sellers need; we cover that decision in our guide to direct vs destination charges.
Which Stripe Connect onboarding option should you use?
Stripe currently offers three onboarding options. Stripe recommends the first two because they update automatically when compliance requirements change.
Stripe-hosted onboarding
Stripe-hosted onboarding is a web form hosted by Stripe, carrying your brand name, color and icon, which renders dynamically based on the account's country, business type and requested capabilities. You redirect the seller to it with an Account Link, a short-lived, single-use URL, and they come back to your app when they finish or leave. It takes the least integration effort, supports new countries without code changes, and supports networked onboarding, where owners of several Stripe accounts can reuse information they already provided. The trade-off is limited control over the flow and look.
Embedded onboarding
Embedded onboarding uses the Account onboarding component, one of Stripe's Connect embedded components, rendered inside your own application. Your server creates an Account Session for the connected account with the `account_onboarding` component enabled, your front end initializes Connect.js with the session's client secret, and the component collects requirements without the seller leaving your product. It is highly themeable with limited Stripe branding, and like hosted onboarding it adapts automatically to changing requirements. You get an exit callback instead of a return URL. It takes more effort than hosted onboarding but keeps the seller in your interface.
API onboarding
API onboarding means you build your own forms and submit the information through the Stripe API. You handle identity verification flows, localization and error handling for every country you support, and Stripe says you must plan to review your requirements at least every six months. If you collect government IDs and tax numbers yourself, you also take on the security and regulatory weight of handling that data. Stripe does not recommend this route unless you are committed to maintaining it; for a customized flow it points to embedded onboarding instead.
- Choose Stripe-hosted when speed matters more than look and feel, and a redirect away from your app is acceptable.
- Choose embedded when sellers should stay inside your product and you want your own styling, without owning requirement logic.
- Choose API onboarding only when you need full control of every screen and have a team that will keep up with requirement changes in every country you serve.
How the Stripe Connect onboarding flow works with Account Links
For Stripe-hosted onboarding, the flow has five steps, and two of them are the ones teams get wrong. First, create the connected account once per seller and store its ID against the seller; never create a new account every time someone clicks a button. Pre-fill whatever you already know, such as the business URL or a product description, before you generate a link, because you cannot read or write the account's information through the link afterwards. Second, when the authenticated seller asks to set up payouts, your server creates a fresh Stripe Connect onboarding link with the Account Links API and redirects them to it. Third, the seller completes Stripe's form. Fourth, Stripe sends them back to your `return_url` or your `refresh_url`. Fifth, your server works out where the account actually stands.
What return_url means, and what it does not
Stripe redirects the seller to the `return_url` when they complete the flow or click to save and continue later. Stripe is explicit that this does not mean all information has been collected or that the account has no outstanding requirements; it only means the seller entered and exited the flow properly. No state is passed in the URL. On return, retrieve the account (or read the state you cached from webhooks) and check the requirements before you show anything. If onboarding is incomplete, show the seller what is left and a way to continue.
What refresh_url means
Stripe redirects the seller to the `refresh_url` when the link is no longer valid: it expired (Account Links last only a few minutes), it was already visited (the seller refreshed or used the back button), or it was opened by something else first, such as a messaging app generating a link preview. Your `refresh_url` should be a route on your server that creates a new Account Link with the same parameters and redirects the seller to it. It is not an error page. In live mode, both URLs must use HTTPS.
Two rules follow from links being single-use and short-lived. Generate the link at the moment of the click, never in advance. And never email, text or otherwise send Account Link URLs outside your app; Stripe says to provide them only to the authenticated account holder inside your application. If you want to remind a seller by email, link to a page in your app that generates the Account Link after they sign in.
Here is the server side of that flow in Node.js. The route generates a new link on every request, which is exactly what the `refresh_url` route should do as well. The `collection_options.fields` value is a product decision, explained in the next section.
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
const APP_URL = process.env.APP_URL; // HTTPS in live mode
// Also mounted at the refresh_url, so an expired link just becomes a new one.
app.get("/payouts/onboarding", requireSignedInSeller, async (req, res) => {
const accountId = await getOrCreateConnectedAccount(req.seller); // one per seller
const link = await stripe.accountLinks.create({
account: accountId,
type: "account_onboarding",
refresh_url: APP_URL + "/payouts/onboarding",
return_url: APP_URL + "/payouts/onboarding/return",
collection_options: { fields: "currently_due" }, // or "eventually_due"
});
res.redirect(303, link.url); // never store, email or log this URL
});Stripe Connect onboarding requirements: currently_due, eventually_due and past_due
The `requirements` hash on the Account object is the most important structure in Connect onboarding. Each entry is a field path such as `company.tax_id` or `external_account`. Learn what each list means and most onboarding bugs disappear.
- `currently_due`: requirements that must be resolved by `current_deadline` for the account to stay active. If this list is not empty, the account has outstanding requirements that may restrict its capabilities.
- `eventually_due`: requirements that may become required later, for example once the account crosses a volume threshold. When one becomes required, it appears in `currently_due` as well.
- `past_due`: requirements that were not resolved before `current_deadline` and have disabled capabilities. It is a subset of `currently_due`.
- `pending_verification`: information Stripe is verifying asynchronously. Nothing for the seller to do yet, but the outcome may move a requirement back to `currently_due`.
- `current_deadline`: a Unix timestamp; the earliest deadline across the account's capabilities and risk requirements.
- `errors`: why a submitted value failed validation or verification, with a code and a plain-language reason you can show or localize.
- `disabled_reason`: why the account cannot currently process charges or transfers, for example `requirements.past_due`, `requirements.pending_verification`, `under_review` or one of the `rejected.*` values.
Capabilities carry their own status and their own requirements, which you can read through the Capabilities API. A seller can have `transfers` active while `card_payments` is not, so check the capabilities your charge model actually uses rather than relying on a single flag. A marketplace that only pays sellers through transfers cares about different capabilities from a SaaS platform using direct charges.
Up-front or incremental onboarding?
When you create an Account Link, `collection_options.fields` decides how much you ask for. Incremental onboarding (`currently_due`) collects only what is needed now, so sellers get through faster. Up-front onboarding (`eventually_due`) collects everything the account may need, which usually means one session instead of several and avoids payouts being interrupted later by a missed deadline. Stripe also notes up-front collection exposes risk earlier, because accounts that will not provide information reveal it at sign-up. Neither is universally better. Sellers who are testing your platform tolerate less friction; sellers who expect steady volume benefit from finishing once.
What happens when payouts are paused?
If `currently_due` still has entries when `current_deadline` arrives, Stripe disables the corresponding functionality and moves those entries to `past_due`. According to Stripe, it typically disables payouts first; other consequences can apply, and if payouts are already disabled and the account does not respond, Stripe may also disable charges. For a seller this looks like money they earned not arriving. For your support team it looks like an angry email. Your product should surface paused payouts before the seller notices, explain in plain words what is needed, and send them straight into onboarding to fix it. Once the requirements are satisfied and verified, the capability is restored; your code should learn about that from `account.updated`, not by polling.
Why account.updated is your source of truth
Stripe's guidance for every onboarding option is the same: listen for the `account.updated` event and keep the account's state cached in your application. It fires when requirements change, when verification succeeds or fails, when capabilities change status and when a seller updates their own details. If you use the Persons API, `person.updated` matters too. Events about connected accounts are delivered to a Connect webhook endpoint, which is configured separately from the endpoint that receives events about your own platform account. A common bug is building a perfect handler on the wrong endpoint and never receiving a single connected-account event.
Treat the event as a signal rather than a complete record. Events can arrive more than once and out of order, so a robust handler records the event ID to ignore duplicates, then retrieves the current Account object from Stripe and recomputes the seller's status from that, rather than applying the event payload as a diff. That way, an old event delivered late cannot overwrite a newer state. Our guide to Stripe webhooks best practices covers signature verification, idempotency and retries in detail. If your platform has moved to the Accounts v2 API, Stripe also emits v2 event types for account changes, such as identity and person updates; subscribe to the ones your integration relies on and apply the same discipline.
Do not rely on the `return_url` alone. A seller can close the tab before the redirect, finish on another device, or never return at all while Stripe's verification completes hours later. The webhook is the only signal that reliably arrives.
Building a seller status UX that sellers understand
Sellers do not want to read `requirements.currently_due`. They want to know whether they can get paid, and if not, what to do. That means your platform needs its own small set of statuses, derived from Stripe's data, with a clear message and a single next action for each. Five usually cover it:
- Not started. No connected account yet, or the seller never opened the flow. Action: “Set up payouts”.
- In progress. The account exists but `details_submitted` is false, or `currently_due` has entries that block what the seller needs. Action: “Continue setup”, which creates a fresh link.
- Pending verification. Details are submitted and Stripe is reviewing them (`pending_verification` has entries, or the disabled reason says so). Action: none; tell the seller what is being checked and that you will notify them.
- Enabled. The capabilities your charge model needs are active and nothing is due. Optionally show an “action needed soon” banner when `currently_due` has entries with a future deadline while the account still works.
- Restricted. `past_due` has entries, payouts or charges are disabled, or the account was rejected. Action: “Fix now” for remediable cases; a support route for rejections, which sellers cannot fix by filling in a form.
Keep the mapping in one pure function that takes an Account object and returns your status, and call it from the webhook handler, from the `return_url` handler and from any admin tool. One function means support and sellers always see the same answer. Here is a starting point for the v1 Account shape; adjust which capabilities count as “enabled” for your charge model.
// needs: the capabilities your charge model relies on, e.g. ["transfers"].
export function sellerStatus(account, needs = ["card_payments", "transfers"]) {
if (!account) return { status: "not_started" };
const r = account.requirements || {};
const reason = r.disabled_reason || "";
const caps = account.capabilities || {};
const allActive = needs.every((c) => caps[c] === "active");
if ((r.past_due || []).length || reason.startsWith("rejected") || reason === "listed") {
return { status: "restricted", reason, due: r.past_due || [] };
}
if (!account.details_submitted) {
return { status: "in_progress", due: r.currently_due || [] };
}
if ((r.pending_verification || []).length || !allActive) {
if ((r.currently_due || []).length) return { status: "in_progress", due: r.currently_due };
return { status: "pending_verification" };
}
return {
status: "enabled",
actionDue: (r.currently_due || []).length > 0, // show a banner, not a block
deadline: r.current_deadline || null,
};
}Store the status, the due list and the deadline on your seller record, and log every transition with the event ID that caused it. When a seller writes in asking why they have not been paid, that history answers the question in seconds.
Re-onboarding when requirements change
Requirements change for three common reasons: the seller's volume crosses a threshold and an `eventually_due` item becomes `currently_due`; a verification fails and the item returns with an error; or Stripe introduces new requirements, for example when regulations change. Stripe notifies platforms about upcoming requirement updates, and you can see them ahead of time in the account's `future_requirements` hash. When you are responsible for requirement collection, you can include them in onboarding with `collection_options.future_requirements` set to `include`.
The re-onboarding mechanism is the same as onboarding. Stripe's guidance is to send a connected account back through onboarding whenever it has `currently_due` or `eventually_due` requirements; the onboarding UI knows what to collect, so you do not need to map field paths to screens yourself. For Stripe-hosted onboarding, that is a new Account Link of type `account_onboarding`. Links of type `account_update`, which let sellers edit information they already provided, are only available where your platform is responsible for requirement collection, and not for accounts with access to a Stripe-hosted dashboard. With embedded components, the Account onboarding component handles both cases, and the notification banner component can prompt sellers about outstanding requirements inside your product.
- Prompt early. As soon as `currently_due` gains entries with a deadline, show an in-app banner and send a reminder that links to your app, not to Stripe.
- Escalate as the deadline nears. A second reminder, then a blocking prompt on the seller dashboard. Restricted payouts are much harder to explain after the fact.
- Handle risk reviews. Some requirements from Stripe risk reviews cannot be provided through the API. Stripe-hosted onboarding, embedded components or a remediation link from your Dashboard can collect them.
- Tell the seller what changed. “We need one more document to keep your payouts running” is better than an unexplained form.
How to reduce onboarding drop-off without inventing numbers
You will find plenty of confident percentages about onboarding conversion online. We do not have trustworthy ones for your business, and neither does anyone who has not measured your funnel. What we can offer is a list of changes that remove real friction, and a suggestion to measure each step yourself: link created, flow opened, returned, details submitted, enabled.
- Ask at the moment of value. Prompt a seller to set up payouts when they have a booking or a sale waiting, not on the first screen after sign-up.
- Request only the capabilities you need. Stripe's onboarding collects requirements for every requested capability, so an unnecessary capability adds questions.
- Pre-fill what you know. Name, address, business URL or product description provided when you create the account shortens the form; the seller still confirms it before accepting the service agreement.
- Explain before you redirect. One screen that says what Stripe will ask for (identity, business details, bank account) and why sets expectations and reduces abandonment at the ID step.
- Make resuming trivial. Sellers can save and finish later. A visible “Continue setup” button that generates a fresh link is all it takes.
- Choose the collection mode deliberately. Incremental is faster today; up-front avoids interrupting payouts later.
- Keep the seller in context. If the redirect to a hosted page is where you lose people, embedded onboarding keeps them in your product.
Stripe Connect onboarding in mobile and React Native apps
Mobile apps add one hard constraint. Stripe states that Stripe-hosted onboarding is supported only in web browsers and cannot be used in embedded web views inside mobile or desktop applications. Stripe's own native examples open the Account Link in an in-app browser tab, `SFSafariViewController` on iOS and Custom Tabs on Android, rather than a WebView. The same principle applies to React Native and other cross-platform frameworks: open the link with the platform's in-app browser or the system browser, not a WebView component.
Coming back into the app needs planning. Your `return_url` and `refresh_url` must be HTTPS in live mode, so a common approach is a small page on your domain that hands control back to the app, for example through universal links or app links, and then lets the app fetch the seller's status from your server. Never pass state through the URL; your server already knows which seller started the flow. If the seller dismisses the browser before any redirect, sync with your backend when the app regains focus.
If you want onboarding inside the app itself, Stripe offers Connect embedded components for native mobile SDKs, where the Account onboarding component presents its own authenticated view. Support differs by platform and changes over time, so check Stripe's embedded components documentation for your stack, including React Native, before you commit to an approach. Whichever you choose, account state still arrives through `account.updated` on your server, and the app should only ever display the status your backend computed.
Testing Stripe Connect onboarding in test mode
Stripe provides test values that drive onboarding outcomes in test mode, documented in Testing Connect. Use them to exercise every status in your state machine before launch, not just the happy path.
- Successful verification. The documented test values include `000-000` as the SMS code, `address_full_match` as the address line, `1901-01-01` as a matching date of birth and `000000000` as a matching ID number.
- Failed or pending verification. Values such as `address_no_match` or a non-matching ID number are documented to create `currently_due` requirements, which lets you test the in-progress and error states.
- Requirements appearing later. Stripe documents trigger cards that, used in a Connect charge on an account with `eventually_due` requirements, move the next requirements to currently due or trigger a charge or payout block, which exercises your restricted path.
- Payout failures. Test bank account numbers simulate failures such as a closed or non-existent account, so you can test what sellers see when a payout fails.
- Webhooks locally. The Stripe CLI can forward Connect events to a local endpoint, so you can watch `account.updated` drive status changes end to end.
Two cautions from Stripe's testing guide. Some address tokens cannot be switched to a more restrictive one later on the same account, so create a fresh test account per scenario. And sandboxes may not enforce capabilities, so an account might be able to act even when a capability is not `active`. Your status logic should still read the capability status, because live mode enforces it.
From here, this article is about Cevrynt
How Cevrynt builds Stripe Connect onboarding
Cevrynt is a Stripe partner, a member of the Stripe Partner Ecosystem. Arindam Pradhan, our founder, is a Stripe Certified Professional (Implementation Architect), and Anupam Pradhan and Subhadip Giri are Stripe Certified Professionals (Payments Architect). For Connect onboarding we start with your account configuration and charge model, then build the onboarding journey, the status model, the webhook handlers and the seller-facing screens, tested against every state above, in code you own.
Projects are scoped and priced in writing after a technical review. See our Stripe Connect service and the wider Stripe services overview, or describe your sellers and current flow and we will come back with the questions that matter for your platform.
