Stripe Connect for an EU marketplace: the KYC and payout edge cases that only show up in production
Published 25 October 2026 · Stripe Connect · Marketplaces · EU KYC
Stripe Connect's quickstart gets a marketplace taking payments in an afternoon. What it does not cover is the second month: a provider in Sweden whose payouts stopped, a customer in Spain who wants to add an hour mid-job, a refund that has to claw money back from a connected account that has already been paid out. This is what we learned building and operating Door2Day, a home-services marketplace in Spain and Sweden, with customers paying by card and providers paid out in their local currency.
It is written for the engineer adding Connect to a two-sided platform, so each section ends with what we would do from the start next time.
1. Onboarding: the requirements list is a moving target
Connect accounts are not "verified" once. Stripe attaches a requirements object to every account
with currently_due, eventually_due, past_due and a current_deadline. What is required depends
on the country, the business type (individual vs company) and — the part that surprises people —
how much the account has processed. A Swedish sole trader can onboard with a name, date of birth
and address, start earning, and weeks later be asked for an identity document because a volume
threshold was crossed.
If nothing in your product watches that object, the first you hear of it is a provider asking why their money stopped.
What we do now:
- Handle the
account.updatedwebhook and storerequirementson our side. Whencurrently_dueis non-empty, the provider sees a banner and gets an email; when acurrent_deadlineis set, the email says the date. - Let Stripe collect the information. Stripe-hosted onboarding (Account Links) already knows what a Spanish company needs versus a Swedish individual — tax IDs, representatives, beneficial owners — and keeps up with regulatory changes we would otherwise have to track ourselves.
- Treat
payouts_enabledandcharges_enabledas separate booleans in the UI. An account can take jobs but not be paid out, and the provider needs to know which one is broken.
2. Payouts across two countries and two currencies
Spain settles in EUR, Sweden in SEK. Customers pay in the currency of the service, and the provider is paid out in their account's default currency. The two facts that drove our design:
- A connected account's payouts happen in its settlement currency. A Swedish provider gets SEK. If the charge was in EUR, Stripe converts, and the conversion rate is Stripe's, not yours.
- Platform and connected account countries matter for which charge types are allowed. Within the EU and for our setup, destination charges worked for both countries; we checked the cross-border rules in the Connect docs for each country pair before promising anyone a payout.
We use destination charges with an application_fee_amount: the customer pays the platform, the
provider's share is transferred automatically, the fee stays with us. One PaymentIntent per job,
transfer_data[destination] set to the provider's account. The alternative — separate charges and
transfers — gives more control and more bookkeeping; we would still start with destination charges
for a single-provider-per-job marketplace.
Payout timing is the other thing to design for. Stripe's default schedule for a new connected
account is daily with a rolling delay that depends on the country. Providers in both countries
asked about this, so the provider app shows the next expected payout date from the payout
objects rather than a generic "within a few days".
3. The mid-job price change
The edge case that made us rewrite the payment flow: a customer books three hours of cleaning, the job runs long, and both sides agree on a fourth hour while the cleaner is still in the flat.
Options we considered:
- Authorise more than the booked amount up front and capture the final figure. Card networks allow capturing less than the authorised amount, and this is clean for small overruns. It fails the moment the overrun is larger than the headroom, and customers do not like seeing a hold bigger than the booking.
- Incremental authorisation. Stripe supports increasing an authorisation on eligible cards, but eligibility depends on card brand and issuer, so it cannot be the only path.
- A second PaymentIntent for the extra time, confirmed with the customer's saved payment method. Always works, costs a second transaction fee, and produces two charges the customer sees on their statement.
We ended up with the booking authorised with modest headroom, captured for the agreed amount, and any overrun above the headroom charged as a separate PaymentIntent with the saved card and the same destination account — with the customer confirming the extra in the app before the charge. The provider-side transfer for the extra arrives as its own payout line, which the provider app explains.
4. Refunds, reversals and disputes
- Refunding a destination charge does not automatically take the money back from the provider.
Pass
reverse_transfer: true(andrefund_application_feeif your fee should be returned too), otherwise the platform eats the refund while the provider keeps the transfer. - Reversals can fail if the connected account's balance has already been paid out and is now negative; Stripe pulls it from the next payout, or, for debit-negative-balance countries, from the account. Your support team needs to know this is a timing issue, not a bug.
- Disputes land on the platform for destination charges. Make sure the job's evidence — booking, check-in/check-out timestamps, photos, chat — can be exported in one click, because you have a deadline measured in days and the provider is the one who holds the proof.
Webhooks we treat as mandatory: account.updated, payment_intent.succeeded,
payment_intent.payment_failed, charge.refunded, charge.dispute.created, payout.paid,
payout.failed, transfer.reversed. Every one of them updates our own records; the Stripe
dashboard is never the source of truth for what the app shows.
5. Testing what you cannot reproduce
Stripe's test mode has test identities and test cards for most of these states — accounts that fail verification, cards that trigger disputes, payouts that fail. We keep a script that creates the full set of connected-account states for both countries and runs the webhook handlers against them, and it runs in CI. The things it cannot simulate — a real identity-document review, a real chargeback timeline — go into a runbook with the exact dashboard steps for support.
What we would do from day one next time
- Store the
requirementsobject and surface deadlines to providers; never let a payout stop silently. - Use Stripe-hosted onboarding for KYC in every country; do not build the forms yourself.
- Destination charges with an application fee, one PaymentIntent per job, headroom on the authorisation.
- A documented path for charging more than the booking, agreed in the app before it happens.
reverse_transferon every refund, and a one-click evidence export for disputes.- Every webhook above handled and tested in CI before the first real provider signs up.
Adding payments to a marketplace, or inheriting one that was built fast? A Production-Readiness Audit covers the payment flow, the webhooks and the data behind them — $2,500, fixed price, five business days.
Book a 20-minute call
