python
46 lines · 7 steps
Handling Stripe webhooks in Django
A Django view that verifies Stripe webhook signatures and fulfills paid orders idempotently.
Explained by
highlit
1import json
2import logging
3
4import stripe
5from django.conf import settings
6from django.http import HttpResponse, HttpResponseBadRequest
7from django.views.decorators.csrf import csrf_exempt
8from django.views.decorators.http import require_POST
9
10from .models import Order
11from .tasks import send_receipt_email
12
13logger = logging.getLogger(__name__)
14
15
16@csrf_exempt
17@require_POST
18def stripe_webhook(request):
19 payload = request.body
20 sig_header = request.META.get("HTTP_STRIPE_SIGNATURE")
21
22 try:
23 event = stripe.Webhook.construct_event(
24 payload=payload,
25 sig_header=sig_header,
26 secret=settings.STRIPE_WEBHOOK_SECRET,
27 )
28 except ValueError:
29 logger.warning("Stripe webhook received malformed payload")
30 return HttpResponseBadRequest("Invalid payload")
31 except stripe.error.SignatureVerificationError:
32 logger.warning("Stripe webhook signature verification failed")
33 return HttpResponseBadRequest("Invalid signature")
34
35 if event.type == "checkout.session.completed":
36 session = event.data.object
37 order = Order.objects.filter(
38 stripe_session_id=session.id, status=Order.Status.PENDING
39 ).first()
40 if order is not None:
41 order.mark_paid(payment_intent=session.payment_intent)
42 send_receipt_email.delay(order.pk)
43 else:
44 logger.info("Ignoring unhandled Stripe event %s", event.type)
45
46 return HttpResponse(status=200)
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Always verify webhook signatures against a shared secret before trusting the payload.
- 2Filtering on a pending status makes fulfillment idempotent against duplicate webhook deliveries.
- 3Offload slow side effects like email to a background task and return 200 promptly.
Related explainers
python
import argparse import sys from pathlib import Path
Building a subcommand CLI with argparse
cli
argparse
subcommands
Intermediate
6 steps
python
from collections.abc import Mapping from typing import Any, Iterator
Flattening nested config into dotted keys
recursion
generators
tree-traversal
Intermediate
7 steps
python
import csv import io from datetime import datetime
Streaming a CSV export in Flask
streaming
generators
csv
Intermediate
9 steps
python
import time from collections import defaultdict from threading import Lock
Sliding-window login rate limiting in Flask
rate-limiting
sliding-window
thread-safety
Intermediate
7 steps
python
from django.conf import settings from django.contrib.auth import get_user_model from django.core.mail import EmailMultiAlternatives from django.db.models.signals import post_save
Sending a welcome email with Django signals
signals
email
user-activation
Intermediate
8 steps
python
import csv import io from datetime import date
Streaming a CSV export in FastAPI
streaming
async-generators
csv
Advanced
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/handling-stripe-webhooks-in-django-explained-python-e56e/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.