← The Feed

The Stripe Connect onboarding rabbit hole nobody warns you about

March 30, 2026

Stripe Connect Express looks simple from the docs. stripe.accounts.create, return an onboarding link, the seller fills in their info, you get a webhook saying "account ready," you start charging cards on their behalf. Maybe an afternoon of work.

The first time you onboard a real seller, you discover four things the docs do not warn you about. I am writing this down because I had to learn each one the painful way while building instxnt's Stripe integration.

1. The "account.updated" webhook fires a lot, and not always when you think

You wire up account.updatedas your signal for "onboarding complete." It fires. Your code marks the seller as ready. You start letting them publish storefronts. Then you discover that account.updated also fires when:

  • The seller adds a new bank account.
  • Stripe asks for additional verification (passport upload, etc.) two weeks later.
  • The seller's account gets paused for fraud review.
  • Random metadata changes you do not care about.

The fix: do not treat account.updated as a binary. Read capabilities.card_payments and capabilities.transfers. Both have to be active for the seller to actually be able to receive money. Both can flip from active back to pendinglater. Your code needs to handle the "was active, now pending" case — usually by pausing the seller's published storefronts and showing them what Stripe is asking for.

2. requirements.currently_due lies to you about urgency

Stripe's account object has a requirements field with currently_due, past_due, eventually_due, and pending_verificationarrays. The natural read is "currently_due is the urgent stuff."

It is not. currently_due contains things Stripe needs in the next few weeks before they pause the account. past_due contains things the account has already been paused for. By the time something is in past_due, the seller is already failing payouts.

The actually-useful read is: surface currently_duein the seller's dashboard with a specific deadline Stripe gave you (it is in the requirements.current_deadline field). If you wait until past_due to nudge the seller, you have already lost a day or two of their sales to a paused account.

3. OAuth state tokens have to survive Safari's cross-site cookies

The Connect onboarding flow opens a Stripe-hosted page (the "Express onboarding" flow). Stripe redirects back to your callback URL when the seller is done. You verify the OAuth state token to prevent CSRF and session-fixation attacks.

On Safari and Brave, the cookie you used to store that state token will be missing on the callback. Cross-site cookies. Same-site SameSite policies. ITP. The browser dropped your cookie between the redirect to Stripe and the redirect back.

The fix that works for me on instxnt: store the OAuth state in a server-side ephemeral store (we use D1 with a 10-minute TTL), keyed by a random token that goes in the URL itselfas part of the redirect. The state token round-trips through Stripe's URL, not through a cookie. Cookies just are not reliable for this on Safari.

The Stripe docs hint at this ("you may need to use a server-side session store") but they do not warn you that the cookie path will silently fail in Safari and you will only find out when a frustrated user emails you.

4. Standard accounts vs Express vs Custom is a one-way decision

Stripe Connect comes in three flavors: Standard, Express, Custom. They look like a gradient — "less your responsibility" on one end, "more flexibility" on the other. They are not actually a gradient; they are three completely different APIs with three completely different liability profiles, and you cannot easily migrate accounts between them.

I chose Express on instxnt because:

  • The seller still has a real Stripe dashboard (good for refund handling, tax docs, dispute management — all the things I do not want to be in the middle of).
  • We collect platform fees via application_fee_amount on each charge, so the accounting is clean.
  • Stripe handles compliance, KYC, and tax forms — instxnt is not on the hook for being a money transmitter.

The downside: I cannot fully whitelabel the Stripe-hosted onboarding. The buyer-facing checkout is fine — that's on instxnt's domain. But the seller's Stripe-side experience has Stripe branding, and there is nothing I can do about it.

If you need a fully whitelabeled experience, you have to use Custom accounts and accept the compliance burden that comes with them. That is a different kind of company than the one we are building.

The summary, if you are about to integrate Connect

  1. Treat account.updatedwebhooks as "state changed, re-evaluate everything," not "onboarding complete."
  2. Show currently_due to sellers with the specific deadline. Do not wait for past_due.
  3. Round-trip your OAuth state through the URL, not a cookie. Safari users matter.
  4. Pick Standard / Express / Custom carefully. Migrating between them later is painful.

None of this is in the "quickstart" section of the Stripe docs. All of it is in the source if you grep around enough, or in the support forum if you search for the symptom. I wrote it down so the next person does not have to learn the same way.

If you want to see what a fully wired Connect integration looks like in production, the instxnt seller flow is at /integrations/stripe and the buyer-side payment docs are at /docs/payments.