guide

Stripe Billing for Django SaaS

Integrate Stripe subscriptions, free trials, metered billing, and invoice management into Django SaaS. Complete guide with dj-stripe, webhook handling, and EUR pricing. From EUR 4,000.

TL;DR

Stripe is the billing backbone for 80% of Django SaaS products. A production-ready integration using dj-stripe handles subscriptions, free trials, metered pricing, invoicing, and dunning — typically requiring EUR 4,000-10,000 in development. The critical implementation details are webhook reliability (idempotent handlers with 99.99% processing rate), subscription state management across 12 lifecycle events, and PSD2/SCA-compliant payment flows for European customers.

Stripe + Django Architecture: dj-stripe vs Custom Integration

There are two approaches to integrating Stripe billing into a Django SaaS application. The choice affects development time, maintenance burden, and flexibility.

Option 1: dj-stripe (recommended for most SaaS)

  • dj-stripe (4,000+ GitHub stars) syncs Stripe objects to your Django database as Django models. You query Subscription, Customer, Invoice, Price objects using the Django ORM instead of making API calls.
  • Installation: pip install dj-stripe. Add "djstripe" to INSTALLED_APPS. Set DJSTRIPE_WEBHOOK_SECRET, STRIPE_LIVE_SECRET_KEY, STRIPE_TEST_SECRET_KEY, DJSTRIPE_FOREIGN_KEY_TO_FIELD = "id" in settings.
  • Initial sync: python manage.py djstripe_sync_models pulls all existing Stripe data into your database. For a SaaS with 500 customers, initial sync takes 2-5 minutes.
  • Webhook handling: dj-stripe provides a built-in webhook endpoint at /djstripe/webhook/. It verifies signatures, creates/updates local model instances, and fires Django signals you can connect to.
  • Pros: Local database queries (fast, no API latency), Django admin integration, built-in webhook processing, handles 95% of billing use cases out of the box
  • Cons: Adds 30+ database tables, sync lag (seconds) between Stripe event and local update, opinionated model structure
  • Development time: 2-3 weeks for full subscription billing. Cost: EUR 4,000-6,000.

Option 2: Custom Stripe integration

  • Use the stripe Python SDK directly. Store only the fields you need (customer ID, subscription ID, status) in your own models. Call Stripe API for everything else.
  • Pros: Full control over data model, no sync complexity, minimal database footprint
  • Cons: More code to write and maintain, must handle webhook processing manually, API calls add latency to user-facing operations
  • Best for: Simple billing (one plan, no metering), or highly custom billing logic that dj-stripe cannot accommodate
  • Development time: 3-5 weeks. Cost: EUR 6,000-10,000.

Recommended stack: dj-stripe + Stripe Checkout (hosted payment page) + Stripe Customer Portal (self-service plan changes) + Stripe Billing (invoicing and dunning). This combination covers 95% of SaaS billing needs with minimal custom code.

Subscription Lifecycle: Trials, Upgrades, Downgrades, and Cancellations

A Stripe subscription moves through 12 distinct states during its lifecycle. Your Django application must handle each state correctly to avoid revenue leakage and poor user experience.

Free trial implementation:

  • Set trial_period_days on the Stripe Price object (e.g., 14 days) or pass trial_end timestamp when creating a subscription.
  • No card upfront: Create subscription with payment_behavior="default_incomplete" and no payment method. User gets full access for trial duration. Convert to paid via Stripe Checkout session at trial end.
  • Card upfront (higher conversion): Collect card via Stripe Checkout with subscription_data={"trial_period_days": 14}. Card is charged automatically when trial ends. 25-40% higher trial-to-paid conversion than no-card trials.
  • Trial expiry handling: Listen for customer.subscription.updated webhook where status changes from trialing to active (payment succeeded) or past_due (payment failed). Downgrade features accordingly.

Plan changes (upgrades and downgrades):

  • Immediate upgrade: stripe.Subscription.modify(sub_id, items=[{"id": si_id, "price": new_price_id}], proration_behavior="create_prorations"). Stripe calculates the prorated amount and charges immediately.
  • Downgrade at period end: stripe.Subscription.modify(sub_id, items=[...], proration_behavior="none", billing_cycle_anchor="unchanged"). User keeps current plan until the end of the billing period.
  • Feature gating in Django: Create a PlanFeatures model mapping plans to feature flags. Middleware or decorator checks: @require_plan_feature("api_access"). Cache plan features in Redis to avoid database queries on every request.

Cancellation flow:

  • Cancel at period end (recommended): stripe.Subscription.modify(sub_id, cancel_at_period_end=True). User keeps access until the paid period ends. Show "Your plan will cancel on {date}" banner.
  • Immediate cancellation: stripe.Subscription.delete(sub_id). Issue prorated refund if applicable. Downgrade to free tier immediately.
  • Cancellation survey: Before processing cancellation, show a modal with reasons (too expensive, missing feature, switching competitor, other). Store responses for product analytics. Offer alternatives: pause subscription, downgrade to cheaper plan, extended trial.
  • Win-back: Celery task sends reactivation email 7, 30, and 60 days after cancellation with incentive (e.g., 20% discount for 3 months).

Dunning (failed payment recovery):

  • Configure Stripe Smart Retries in Dashboard (retries failed payments up to 4 times over 3 weeks using ML-optimised timing).
  • Listen for invoice.payment_failed webhook. Send in-app notification and email: "Your payment failed. Please update your card to keep your account active."
  • After final retry failure (customer.subscription.deleted event), downgrade to free tier and send account suspension notice.
  • Revenue recovery rate with Smart Retries + email notifications: typically 40-60% of initially failed payments.

Metered and Usage-Based Billing

Usage-based pricing is the fastest-growing SaaS billing model — 61% of SaaS companies now offer some form of usage pricing (OpenView 2024). Stripe supports metered billing natively, and Django tracks usage via a reporting pipeline.

Metered billing architecture:

  1. Usage tracking: Every billable event (API call, email sent, document processed, compute minute) is recorded in a UsageEvent model: tenant, event_type, quantity, timestamp, metadata.
  2. Usage aggregation: Celery Beat task runs hourly, aggregating usage per tenant per metered price. Stores aggregated totals in UsageSummary model.
  3. Stripe reporting: At the end of each billing period (or hourly for real-time billing), report usage to Stripe: stripe.SubscriptionItem.create_usage_record(si_id, quantity=total, timestamp=now, action="set").
  4. Invoice generation: Stripe calculates charges based on reported usage and the price per unit. Invoice is generated and charged automatically.

Common metered pricing models:

  • Per-unit pricing: EUR 0.01 per API call, EUR 0.05 per email sent. Simple and predictable. Configure in Stripe as recurring[usage_type]=metered with unit_amount=1 (EUR 0.01).
  • Tiered pricing: First 1,000 API calls at EUR 0.01, next 9,000 at EUR 0.005, above 10,000 at EUR 0.002. Configure as Stripe tiered pricing with graduated tiers.
  • Volume pricing: All units priced at the tier reached. 500 calls = EUR 0.01 each. 1,500 calls = all at EUR 0.005 each. Creates incentive to use more.
  • Hybrid: Base platform fee (EUR 49/month) + metered usage on top. Most popular SaaS pricing model. Two Stripe Price objects on one Subscription: one flat-rate, one metered.

Usage limits and overage protection:

  • Set soft limits: warn at 80% and 90% of included quota via email and in-app notification.
  • Set hard limits: middleware checks UsageSummary against plan limits. Return 429 with upgrade prompt when limit reached.
  • Real-time usage dashboard: Redis counter incremented on each billable event. INCR tenant:{id}:api_calls:{YYYY-MM}. Display in user dashboard with gauge visualisation.
  • Cost per event tracking: For AI-powered features, track actual API cost (e.g., OpenAI token usage) per tenant to ensure pricing covers costs. Store in UsageEvent.metadata.

Implementation cost: Metered billing integration: EUR 2,000-4,000 on top of base subscription billing. Usage tracking infrastructure + Stripe metering + dashboard: 2-3 weeks additional development.

Webhook Reliability and Payment Security

Webhooks are the nervous system of Stripe billing integration. A missed or mishandled webhook means lost revenue, incorrect access, or security vulnerabilities. Here is how to achieve 99.99% webhook processing reliability.

Webhook endpoint hardening:

  • Signature verification: Always verify Stripe-Signature header using stripe.Webhook.construct_event(payload, sig, endpoint_secret). Never process unverified webhooks — attackers can forge subscription activation events.
  • Idempotency: Store processed event IDs in a ProcessedWebhookEvent model. Check before processing: if ProcessedWebhookEvent.objects.filter(stripe_event_id=event.id).exists(): return HttpResponse(200). Stripe retries failed webhooks up to 3 days — without idempotency, you will double-process events.
  • Quick response: Return HTTP 200 within 5 seconds. Process the event asynchronously via Celery: process_stripe_event.delay(event.id, event.type, event.data). Stripe marks webhooks as failed after 20 seconds, triggering retries.
  • Error handling: Catch all exceptions in the webhook handler. Log the full event payload on error. Return 200 even on processing failure (to prevent retry storm) and queue for manual review.

Critical webhook events to handle:

  • checkout.session.completed — activate subscription after Checkout
  • customer.subscription.created — provision tenant features
  • customer.subscription.updated — handle plan changes, trial end
  • customer.subscription.deleted — downgrade to free tier
  • invoice.payment_succeeded — update paid_until, reset usage counters
  • invoice.payment_failed — trigger dunning notifications
  • customer.subscription.trial_will_end — send trial ending reminder (3 days before)
  • payment_method.attached — update default payment method

PSD2/SCA compliance (required for European payments):

  • Strong Customer Authentication (SCA) requires 3D Secure for many European card payments. Stripe Checkout and Payment Intents handle SCA automatically.
  • For subscription renewals, Stripe applies exemptions where possible (merchant-initiated transactions, low-value payments). When SCA is required on renewal, Stripe sends invoice.payment_action_required webhook — your app must email the customer a link to complete authentication.
  • Off-session payments: When creating a subscription, pass off_session=True and payment_method=pm_id. Stripe will attempt the charge and trigger SCA only when required by the bank.

Testing webhooks locally:

  • Use Stripe CLI: stripe listen --forward-to localhost:8000/djstripe/webhook/
  • Trigger test events: stripe trigger customer.subscription.created
  • Write pytest fixtures with stripe.WebhookEndpoint mock events for CI/CD pipeline testing.

Billing Integration Packages and Costs

Essential Subscription Billing (EUR 4,000-6,000):

  • Stripe Checkout integration for payment collection
  • dj-stripe setup with webhook handling
  • 3-5 subscription plans with feature gating
  • Free trial support (configurable duration)
  • Stripe Customer Portal for self-service (plan changes, card updates, invoices)
  • Basic dunning (email on failed payment)
  • Subscription status middleware (restrict access for expired/cancelled)
  • Timeline: 3-4 weeks

Advanced Billing System (EUR 6,000-10,000):

  • Everything in Essential
  • Metered/usage-based billing with real-time tracking
  • Hybrid pricing (base fee + usage)
  • Tiered and volume pricing support
  • Prorated upgrades and downgrades
  • Usage dashboard with limits and alerts
  • Cancellation flow with survey and win-back automation
  • Multi-currency support (EUR, USD, GBP)
  • Invoice customisation with company branding
  • Revenue analytics dashboard (MRR, churn, LTV)
  • Timeline: 5-7 weeks

Enterprise Billing (EUR 10,000-18,000):

  • Everything in Advanced
  • Custom contract pricing (per-customer pricing with Stripe quotes)
  • Annual billing with custom invoicing
  • Tax calculation (Stripe Tax or TaxJar integration for EU VAT)
  • Multi-entity billing (different Stripe accounts per region)
  • Revenue recognition integration (Stripe Revenue Recognition)
  • SOC 2 compliant implementation with audit logging
  • Timeline: 8-12 weeks

Stripe fees to budget for:

  • Stripe processing: 1.5% + EUR 0.25 per European card transaction (2.9% + EUR 0.25 for non-European cards)
  • Stripe Billing: 0.5% of recurring revenue (on top of processing fees)
  • Stripe Tax: 0.5% per transaction (if using Stripe Tax for VAT calculation)
  • For a SaaS with EUR 10,000 MRR: approximately EUR 250/month in Stripe fees (2.5% effective rate)

EU VAT compliance:

  • B2C SaaS selling to EU consumers must charge local VAT rates (19-27% depending on country). Use Stripe Tax to calculate automatically.
  • B2B SaaS: reverse charge mechanism applies for cross-border EU sales. Collect VAT ID, validate via VIES API, apply 0% VAT on invoice.
  • Implementation cost for full VAT compliance: EUR 1,500-3,000 additional. This includes VAT ID validation, correct invoice formatting, and tax reporting exports.

Frequently Asked Questions

Should I use Stripe Checkout or build a custom payment form?

Use Stripe Checkout for 90% of SaaS products. It is a hosted payment page maintained by Stripe that handles card input, 3D Secure, Apple Pay, Google Pay, SEPA Direct Debit, and PSD2/SCA compliance out of the box. It converts 3-5% better than custom forms because Stripe continuously optimises it. Build a custom form with Stripe Elements only if you need the payment flow embedded in your application UI without any redirects — but expect 2-3 weeks additional development and ongoing PCI compliance responsibility.

How do I handle EU VAT for my SaaS?

For B2B SaaS (most common): collect the customer company name, address, and VAT ID during checkout. Validate the VAT ID against the EU VIES database. If valid, apply reverse charge (0% VAT) and note it on the invoice. For B2C SaaS: use Stripe Tax (0.5% per transaction) to automatically calculate and apply the correct local VAT rate based on the customer location. You must register for VAT OSS (One Stop Shop) in one EU country to report and remit VAT for all EU sales.

What happens if a webhook is missed or fails?

Stripe retries failed webhooks with exponential backoff over 72 hours (up to 16 retry attempts). With dj-stripe and idempotent handlers, even retried events are processed correctly without duplication. As a safety net, implement a reconciliation Celery task that runs every 6 hours comparing local subscription states with Stripe API data and fixing any discrepancies. In practice, with proper webhook handling, the miss rate is below 0.01%.

Integrate Stripe Billing into Your Django SaaS

Describe your pricing model (flat-rate, tiered, metered, or hybrid) and I will design the billing architecture and provide a fixed-price quote.

Get a Billing Integration Quote

or message directly: Telegram · Email