Webhooks, Django Models, and Subscription Logic
Accepting payment information is only one aspect of integrating Stripe into Django for payments. Asynchronous event handling, where Stripe notifies your app of changes in payment status, subscription updates, refunds, or disputes, is the most important component of a strong payment system. Webhooks are used to do this. A dependable payment workflow depends on handling them in Django correctly and structuring your database to mirror Stripe's payment lifetime.
What Are Stripe Webhooks?
A webhook is a way for Stripe to send your Django backend real-time notifications about events happening in their system related to your account. Examples include:
- A payment has succeeded or failed
- A customer subscription was created, updated, or canceled
- A refund was issued
- A dispute was opened on a payment
Since payment flows are frequently asynchronous, this is crucial. Without confirming the real payment status from Stripe's servers, you cannot completely trust the frontend, even if it receives confirmation that the user finished checkout. A dependable and secure callback mechanism is offered by webhooks.
Handling Webhooks in Django
Handling webhooks involves creating an API endpoint in Django that Stripe can POST event payloads to. Here’s the high-level flow:
- Create a webhook endpoint URL in your Django app (e.g.,
/stripe/webhook/
). - Configure Stripe (via dashboard or API) to send events to this URL.
- Verify the webhook signature to ensure the request truly comes from Stripe.
- Parse the event payload to extract event type and relevant data.
- Implement logic to update your database based on event type.
- Return a 200 OK response to Stripe to acknowledge receipt.
Since the call originates from an external service rather than a browser, Django views that handle webhooks usually utilize @csrf_exempt. Stripe gives you a secret key (which is different from your API secret key) to validate the webhook signature, which you should keep secure in your settings.
Example process (theory, not code):
- Receive the raw request body.
- Use Stripe’s Python SDK to verify signature against your webhook secret.
- Deserialize the event JSON.
- Match
event.type
to handle cases like"invoice.payment_succeeded"
,"payment_intent.succeeded"
,"customer.subscription.deleted"
, etc. - Perform related database operations (e.g., mark a payment as paid, update subscription status).
- Respond with HTTP 200.
Designing Models for Users, Payments, and Subscriptions
To effectively reflect Stripe’s payment ecosystem in your Django app, your models should track:
1. Customer/User Linkage
You will likely want to store the Stripe customer_id
tied to your Django User
model. This allows you to retrieve or create Stripe customers and associate payments/subscriptions properly.
Example fields:
- User (OneToOne or ForeignKey)
stripe_customer_id
(CharField)
2. Payment Model
A Payment model tracks each transaction:
stripe_payment_intent_id
— to identify the payment in Stripe- Amount and currency
- Status (pending, succeeded, failed, refunded)
created_at
andupdated_at
- ForeignKey to the user or customer
This helps reconcile your records with Stripe’s actual payment outcomes.
3. Subscription Model
For recurring billing, subscriptions have a lifecycle:
stripe_subscription_id
- User
- Status (active, past_due, canceled, unpaid)
current_period_start
andcurrent_period_end
(timestamps)- Plan (can link to your internal plans or products)
Subscriptions change states based on Stripe events — your models must reflect these changes to enable or disable user access accordingly.