We use analytics to understand how our website is used. No personal data is collected.

September 22, 2025 · Piyush Ranjan Mishra

Integrating Stripe Connect and Plaid for a B2B SaaS Platform

StripePlaidPaymentsLoot Discount

At Loot Discount I integrated Stripe Connect for user registration, transfers, and payouts, and Plaid for bank and card integrations consuming financial transaction webhooks — together giving every user instant bank and card onboarding instead of a manual verification step. Payment infrastructure is one of those areas where the quickstart gets you a working demo in an afternoon, and the decisions that actually matter — the ones expensive to reverse once real money and real customers are involved — aren’t covered by any quickstart.

The account-model decision shapes everything downstream

Stripe Connect supports different account types for the platform’s connected users — Standard, Express, and Custom — trading off platform control against platform responsibility for compliance and support. This decision is genuinely expensive to reverse: it shapes onboarding flow, support burden, and compliance exposure from day one, and migrating connected accounts from one model to another after the fact is a real project, not a config change. For a B2B platform where connected accounts were businesses rather than individual consumers, the balance that made sense was enough platform control over the onboarding experience without inheriting the full compliance surface of a fully custom integration.

Webhook idempotency is not optional, and I learned this the expensive way

Stripe and Plaid both redeliver webhook events — on your endpoint timing out, on transient errors, on their own retry policies. Early in the integration, a webhook handler that wasn’t fully idempotent processed a redelivered payment_intent.succeeded event as if it were new, which meant a payment got recorded twice in an internal ledger before validation caught the discrepancy. The fix, applied everywhere after that, was boring and effective: store the provider’s event ID, check for it before processing, treat a duplicate delivery as a no-op success.

const alreadyProcessed = await db.events.findOne({ providerEventId: event.id });
if (alreadyProcessed) return res.status(200).send("already processed");
await db.events.insert({ providerEventId: event.id, processedAt: new Date() });
// ... actual processing, now safe to assume this is the first delivery

This five-line guard, applied consistently to every webhook handler (not just the one that broke), eliminated an entire category of production incident going forward.

The initial Plaid Link flow — connecting a bank account — is the well-documented, well-supported part. The harder, less-discussed part is ongoing transaction sync: webhooks firing as new transactions post, requiring the system to reconcile incremental updates against what it already knows, correctly, indefinitely, for every connected account. The same idempotency discipline that fixed the Stripe webhook issue applied here too — Plaid will redeliver, and transaction processing needs to treat “have I seen this transaction ID before” as a first-class check, not an assumption.

RBAC intersected with payments in ways that needed deliberate design

Once real payment data was in the picture, permissions needed finer granularity than the platform’s general admin/member roles — who can view payout history, who can initiate a transfer, and who can update banking details needed to be distinct, individually auditable permissions, not folded into a generic “admin can do everything” bucket. This is exactly the kind of resource-scoped, capability-based permission modeling I wrote about separately for the platform’s RBAC system — payments was the area where getting that modeling right mattered most, because the cost of a permission mistake here is directly financial, not just a data-visibility issue.

Payout timing is a trust decision, not just a technical one

Stripe Connect payout schedules directly shape connected-account trust and cash-flow expectations, and for a B2B platform this was as much a conversation with the business side as an engineering decision. Getting payout timing unclear or inconsistent damages trust with connected accounts in a way that’s slow to rebuild even after the technical issue is fixed — worth treating as a product decision communicated clearly during onboarding, not a default left unexamined.

What I’d tell someone starting this integration today

Pick your Stripe Connect account type based on your actual compliance appetite and support capacity, not whichever has the fastest quickstart. Build webhook idempotency into the very first handler you write, not after the first duplicate-processing incident forces the issue. And design payment-related permissions as their own granular, auditable capability set from the start — bolting fine-grained permissions onto payments after the fact means auditing every existing code path that touches money.