Stripe Experts
Shopify IntegrationPlatformsIndustriesHire UsBlog
Book a Free Consultation
Stripe Experts

Trusted Stripe integration services for SaaS, marketplaces, and e-commerce.

Company

  • About
  • Blog
  • Contact

Services

  • All Services
  • Stripe Checkout
  • Stripe Connect
  • Stripe Billing

Hire Us

  • All Hiring Options
  • Hire a Stripe Expert
  • Hire a Developer
  • Hire a Stripe Consultant
  • Migrate to Stripe

Industries

  • SaaS
  • Marketplace
  • Healthcare
  • Fintech

Legal

  • Privacy Policy
  • Terms of Service

© 2026 Stripe Experts. All rights reserved.

Not affiliated with Stripe, Inc. Independent Stripe integration partner.

Powered by mzkzeeshan

bussiness@mzkzeeshan.com

  1. Home
  2. Blog
  3. Stripe Tutorials
  4. Subscriptions in Django with Stripe Billing
Stripe Tutorials

Subscriptions in Django with Stripe Billing

Let Stripe own the billing state and Django own the entitlement. Mixing the two is where it goes wrong.

Z

Zeeshan

Founder · Published Aug 10, 2026

The recurring Django-plus-Stripe mistake is modeling too much. Teams build Plan, Subscription, and Invoice tables mirroring Stripe's objects, then spend months keeping two systems in sync. Stripe already is the billing database. Django's job is to answer one question: is this user entitled to this feature right now?

Model the Minimum

class Customer(models.Model):
    user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
    stripe_customer_id = models.CharField(max_length=255, unique=True)

    # Denormalized from Stripe, updated only by webhooks. Never written
    # from a request handler. That is how the two drift apart.
    subscription_status = models.CharField(max_length=32, default="none")
    plan_key = models.CharField(max_length=64, blank=True)
    current_period_end = models.DateTimeField(null=True, blank=True)

    @property
    def has_active_access(self) -> bool:
        return self.subscription_status in {"active", "trialing"}

Three fields, one derived property. Everything else: proration, invoices, payment retries, tax: stays in Stripe where it is already solved.

The Webhook View

Django's CSRF middleware will reject Stripe's POST, and request.body must be read raw. Both are one-liners, but both are required.

@csrf_exempt
@require_POST
def stripe_webhook(request):
    try:
        event = stripe.Webhook.construct_event(
            payload=request.body,  # raw bytes, not request.POST
            sig_header=request.META["HTTP_STRIPE_SIGNATURE"],
            secret=settings.STRIPE_WEBHOOK_SECRET,
        )
    except (ValueError, stripe.error.SignatureVerificationError):
        return HttpResponse(status=400)

    if event["type"].startswith("customer.subscription."):
        sync_subscription(event["data"]["object"])

    return HttpResponse(status=200)

Handle the Whole Lifecycle, Not Just the Happy Path

  • customer.subscription.created / .updated: write status, plan, and period end
  • customer.subscription.deleted: revoke access at the right moment, which is usually period end rather than immediately
  • invoice.payment_failed: the dunning window opens here; do not revoke instantly, Stripe will retry
  • invoice.payment_succeeded: the recovery signal that closes a dunning state

Skipping payment_failed is the most common gap. Without it a card expiry silently downgrades a paying customer, or worse, does not downgrade a churned one.

Check Entitlement, Not Subscription

Gate features on the derived property, never on a live Stripe API call in the request path. A synchronous call to Stripe on every page view adds latency and couples your availability to theirs. The denormalized field, kept current by webhooks, is both faster and more resilient.

#django#billing#webhooks

Stripe insights, monthly

One email a month, no spam, unsubscribe anytime.

No spam, unsubscribe anytime.

On This Page

  • Model the Minimum
  • The Webhook View
  • Handle the Whole Lifecycle, Not Just the Happy Path
  • Check Entitlement, Not Subscription

Related Services

Stripe Billing

Full Stripe Billing implementation: plans, upgrades, dunning, all handled.

Learn more

Webhooks

Production-grade Stripe webhook handling, verified and monitored.

Learn more

API Integration

Custom Stripe API integration for your exact business logic.

Learn more

Related Reading

Stripe Tutorials

Stripe in PHP: Checkout Sessions and Webhook Verification Done Right

The raw-body problem bites PHP integrations harder than most. Here's the correct shape.

Jul 6, 2026Read
Stripe Tutorials

Integrating Stripe Payment Element in React Without the Common Pitfalls

The Elements provider, the client secret lifecycle, and the re-render bug that breaks most first attempts.

Jun 22, 2026Read
Stripe Tutorials

Adding Stripe Payments to a Shopify Store Beyond the Default Plugin

Shopify Payments covers standard storefronts well: here's where it stops being enough.

Apr 13, 2026Read