Stripe Integration Patterns for Multi-Tenant Platforms
Stripe Integration Patterns for Multi-Tenant Platforms
Stripe's API is well-designed for a single business. When your platform manages billing on behalf of dozens or hundreds of businesses, you need patterns that go beyond the getting-started guide.
The multi-key problem
In a multi-tenant setup, each tenant typically has their own Stripe account. That means your backend needs to:
- Store encrypted Stripe secret keys per tenant
- Instantiate the Stripe SDK with the right key for each request
- Validate that incoming webhooks actually came from the expected account
The simplest pattern is a key manager that caches Stripe instances per tenant. When a request comes in for tenant A, you look up their key, create (or reuse) a Stripe client, and make the call. BillStack does this through a getTeamStripe(teamId) function that handles decryption and caching in one place.
Data synchronization
Once you can talk to multiple Stripe accounts, the next challenge is keeping your local database in sync. There are two approaches:
Pull-based (backfill)
When a tenant first connects their Stripe account, you run a one-time backfill that pages through their existing customers, products, prices, and subscriptions. This gives you a local copy of everything, indexed by your own IDs (like pc_ for customers, ps_ for subscriptions).
Push-based (webhooks)
After the initial sync, you rely on Stripe webhooks to keep data current. The tricky part is routing: when a customer.subscription.updated event arrives, you need to figure out which tenant it belongs to, look up the local record, and apply the change.
A common mistake is using a single webhook endpoint per tenant. This doesn't scale. Instead, use one endpoint and route based on the Stripe account ID in the event payload.
Checkout and portal sessions
Stripe Checkout and the Customer Portal work well in multi-tenant setups, but you need to be careful about session creation. Always create sessions server-side using the tenant's Stripe key, and make sure success_url and cancel_url point back to the right tenant's context.
For customer portals, BillStack uses token-based sessions: the platform generates a short-lived token, and the end-user opens a portal that's pre-authenticated for their specific customer record. No separate Stripe portal configuration per tenant required.
API key scoping
When exposing billing functionality through your own API, each API key should be scoped to a specific tenant and set of permissions. A key with customers:read shouldn't be able to create subscriptions. A key for tenant A shouldn't return tenant B's data.
BillStack generates keys with a bs_ prefix followed by 48 hex characters, SHA-256 hashed for storage. Each key is tied to a project and carries optional scopes that the middleware checks on every request.
Webhook signature verification
Every incoming webhook should be verified using the tenant's webhook signing secret — not a global one. Store the signing secret alongside the Stripe API key, and verify the signature before processing any event. This prevents both replay attacks and cross-tenant event injection.
Keep it boring
The best multi-tenant Stripe integrations are the ones that feel boring. No clever tricks, no shared Stripe accounts pretending to be isolated, no event processing that silently drops failures. Just a clear data flow from Stripe through your sync layer into your database, with proper tenant boundaries at every step.