How We Architect Stripe Billing in Bubble: Our Exact Pattern
Stripe billing is the most consequential technical decision in any SaaS. Get it wrong and billing state drifts from reality. Here is our exact pattern: the data model, all six webhook events, and the rule that cannot be broken — webhooks are the only source of truth.
The Stripe Integration Most Developers Get Wrong — And How We Do It
Stripe billing is the most consequential technical decision in any SaaS product. Get it wrong and customers who paid have no access, customers who cancelled retain access, and the billing state drifts from reality whenever the network is unreliable or a browser is closed too soon. Get it right and billing is silent infrastructure that simply works. Here is the exact pattern we implement on every project.
Webhooks Are the Only Source of Truth
How We Model Billing State on the Workspace
subscription_status
→ option set (Trialing, Active, Past_Due, Cancelled, Paused)
plan
→ Plan data type
stripe_customer_id
→ text (set on workspace creation)
stripe_sub_id
→ text (set on checkout.completed webhook)
trial_starts_at
→ date
trial_ends_at
→ date
current_period_end
→ date (updated each invoice.payment_succeeded)
seats_used
→ number (denormalised, updated on member add/remove)
// Plan data type (one record per pricing tier)
Plan
name
→ text (‘Starter’, ‘Growth’, ‘Scale’)
stripe_price_mo
→ text (Stripe Price ID for monthly)
stripe_price_yr
→ text (Stripe Price ID for annual)
seat_limit
→ number (0 = unlimited)
record_limit
→ number (0 = unlimited)
trial_days
→ number
Every Event, Every State Transition
| Webhook Event | What We Do | Workspace Update |
|---|---|---|
| checkout.session.completed | Validate workspace_id from metadata; find Workspace | status=Active, stripe_sub_id=event.subscription |
| customer.subscription.updated | Handle plan change, pause, or resume | status and plan updated from event data |
| customer.subscription.deleted | Mark as cancelled; preserve all data; show reactivation UI | status=Cancelled, cancelled_at=now |
| invoice.payment_failed | Mark as past due; urgent payment update banner | status=Past_Due, payment_failed_at=now |
| invoice.payment_succeeded | Confirm payment; update period end; clear past_due flag | status=Active, current_period_end updated |
| customer.subscription.trial_will_end | Schedule upgrade prompt email 3 days before trial ends | send_trial_ending_email scheduled |
Work With a Bubble Architect
Most developers build Bubble apps. We architect them. Data models designed for scale, multi-tenant security built from day one, Stripe billing that never fails, and workflows engineered for performance. This is what a Bubble Architect delivers.
