← Back

Success Society

A case study in multi-tenant SaaS and payment orchestration

The Problem

SaaS requires coordination across multiple concerns: user authentication, subscription billing, metered usage tracking, generating actual value (leads), and notifying users. A single failure (Stripe doesn't confirm payment, Anthropic API times out, Discord notification fails) can leave the system in an inconsistent state.

Metered billing adds complexity: we charge by the lead generated. If a lead generation request partially succeeds (Anthropic returns results but Stripe times out), have we charged the customer? What's the source of truth? A naive implementation would double-charge or skip charges entirely.

Multi-tenancy adds another layer: one tenant's data must never leak to another, even if application code has bugs. Row-level security must be enforced at the database layer, not just in application logic.

Architecture & Design

The system separates into layers: authentication (Supabase), data (Supabase PostgreSQL with RLS), business logic (lead generation), and external services (Stripe, Anthropic, Discord).

Authentication & Authorization

Supabase Auth handles user signup/login. Each user is associated with a tenant (multi-tenant). Database Row-Level Security (RLS) policies ensure queries automatically filter by authenticated user's tenant. No need to trust application code.

Lead Generation

Anthropic API generates leads based on user criteria. Results are stored in Supabase immediately. This creates a record of what was generated before we charge for it.

Metered Billing

Record usage in Supabase (idempotent: same lead generation request always records the same number of leads). Then sync with Stripe using Stripe's metered billing API. Stripe calculates overages and charges the next billing cycle.

Orchestration

A transaction log tracks each step (lead generated, usage recorded, Stripe notified, Discord message sent). If a step fails, it's retried independently. No cascading failures.

Technical Decisions & Tradeoffs

Supabase for auth and data

Managed PostgreSQL + authentication + real-time subscriptions.

Why: Authentication is notoriously easy to get wrong. Supabase handles password hashing, session management, MFA. One less thing to mess up. PostgreSQL RLS gives data isolation at the database layer—the most trusted place.

Tradeoff: Less control over auth flow (can't customize arbitrarily). But gains security and speed to market. The tradeoff is worth it.

Record usage before charging

Store lead generation records in Supabase first. Then send to Stripe for billing.

Why: Supabase is the source of truth. If Stripe times out, we have a record of what to charge. Retries are safe (Stripe's idempotency keys prevent double-charging).

Tradeoff: Slightly more complex flow (write, then sync). Better than the alternative (charge, then record) where a failure means either missing charge or data loss.

Stripe metered billing, not flat subscriptions

Users pay per lead, not a monthly flat rate.

Why: Fair pricing. A user generating 1 lead doesn't pay the same as someone generating 1,000. Attracts price-conscious customers. Stripe handles all the math.

Tradeoff: Slightly more complex to implement. Worth it for fairness and customer acquisition.

Saga pattern for multi-step workflows

Each lead generation touches multiple services. Track each step in a log.

Why: Distributed transactions are hard. Saga pattern (sequence of steps, each with a redo) is proven. If step 3 fails, we know step 2 succeeded and can retry independently.

Tradeoff: More complex than a single transaction. Necessary when coordinating external services (Stripe, Anthropic, Discord).

Challenges & Solutions

Metered billing reconciliation

Stripe's metered billing API has rate limits. Syncing every lead individually would hit those limits. Also, if the sync fails mid-batch, we'd have partial records.

Solution: Batch usage records. Flush to Stripe every minute (not every lead). Stripe's usage API accepts batch records. Single sync call syncs 100+ leads atomically.

Discord integration reliability

Discord API can be flaky. If the notification fails, should we fail the entire lead generation? Probably not—the lead was already created and paid for.

Solution: Decouple Discord from the main flow. Lead generation returns successfully immediately. Discord notification is best-effort (if it fails, log it, retry later with a background job). Users get leads even if Discord notifications fail.

Row-level security complexity

Writing RLS policies is tricky. One mistake and a tenant reads another tenant's data. Hard to test exhaustively.

Solution: Explicit policies for each table. Default: deny all. Then whitelist specific operations per tenant. Verbose but safe. Use test suite to verify: user A can't read user B's data.

Handling Anthropic API timeouts

Anthropic API sometimes takes >10 seconds. If the lead generation request times out, we've created a usage record but have no leads to show. Customer sees $0.10 charged with nothing to show for it.

Solution: Implement request-level retry with exponential backoff. Timeout after 30 seconds (generous). If API times out, retry up to 2 times. If all retries fail, charge is refunded (manually or automatically through Stripe). Users see transparent error messaging.

Performance & Outcomes

0
Billing errors
Metered billing reconciliation ensures accurate charging, no over/under-billing.
100%
Multi-tenant isolation
Database RLS policies prevent cross-tenant data leaks even with application bugs.
~5s
Lead generation time
Anthropic API request + Supabase write + Stripe sync complete within seconds.
3
External integrations
Stripe, Anthropic, Discord orchestrated reliably with saga pattern.

What this demonstrates: The system successfully coordinated multiple external services (Stripe, Anthropic, Discord) without cascading failures. Metered billing is accurate. Multi-tenancy is enforced at the database layer. The architecture prioritizes data integrity over speed—we record leads before charging, ensuring correctness even if services time out.