# Stripe webhooks on Django

Two Django defaults break Stripe webhooks: CSRF middleware rejects the POST (Stripe sends no CSRF token), and reading `request.POST` gives parsed data. Use `request.body` (raw bytes) and exempt the view. Verification needs no API key, only the signing value.

## The setup

```
import stripe
from django.http import HttpResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST


@csrf_exempt
@require_POST
def stripe_webhook(request):
    sig = request.headers.get('Stripe-Signature')
    try:
        event = stripe.Webhook.construct_event(
            request.body, sig, STRIPE_WEBHOOK_SIGNING_VALUE
        )
    except (ValueError, stripe.error.SignatureVerificationError):
        return HttpResponse('bad signature', status=400)
    # switch on event['type'] ...
    return HttpResponse(status=200)
```

Set `STRIPE_WEBHOOK_SIGNING_VALUE` from your environment at startup. Never hardcode it.

```
from django.urls import path
from .views import stripe_webhook

urlpatterns = [
    path('webhooks/stripe/', stripe_webhook),
]
```

## Notes

- `request.body` is bytes; stripe-python's construct_event accepts bytes directly.
- Never read `request.POST` before verification; parsing first is the classic failure.
- With Django REST Framework, apply the same pattern and skip the parsers for this route.