Stripe Webhook Signature issue in Django
In the 3ee Games production setup using Nginx, Gunicorn with Uvicorn workers, and Docker Compose, I encountered an issue where the Stripe webhook handler was raising a SignatureVerificationError
because the expected Stripe-Signature
header was missing. This article explains the root cause of the issue and the steps to take to resolve it.
The Problem: Header Stripping by Nginx
Stripe sends webhook events with a critical header named Stripe-Signature
that allows your backend to verify the authenticity of the request. In the original implementation, the Django view (via the drf-stripe
package) attempted to access this header via request.META['HTTP_STRIPE_SIGNATURE']
. However, in production the header was not present, leading to an error.
Why Headers Were Missing
Nginx, by default, may drop or rename headers that contain underscores. In this case, it was stripping out the Stripe-Signature
header or not passing it through to the upstream Gunicorn server. Additionally, Gunicorn needed to be configured to correctly handle forwarded headers from Nginx.
The Fix: Proper Header Forwarding
1. Update Nginx Configuration
I modified the Nginx configuration to explicitly forward the Stripe-Signature
header to the Django application. In the Nginx location
block handling the webhook endpoint:
location /some-webhook/ {
proxy_pass http://your-app-name:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Stripe-Signature $http_stripe_signature;
}
This ensured that even if Nginx would normally filter out headers with underscores, it explicitly passes along the Stripe-Signature
header.
2. Configure Gunicorn to Handle Forwarded Headers
Since 3ee Games is using Gunicorn with Uvicorn workers, I needed to ensure that Gunicorn accepted the forwarded headers from Nginx. The Gunicorn command in the Docker Compose file was updated to include the --forwarded-allow-ips="*"
flag:
command: gunicorn sockets.asgi:application -k uvicorn.workers.UvicornWorker -b ipaddress:port --forwarded-allow-ips="*"
The forwarded-allow-ips
flag instructs Gunicorn to trust and process headers such as X-Forwarded-For
and custom headers forwarded from Nginx.
Using the wildcard (*) for Gunicorn’s —forwarded-allow-ips tells Gunicorn to trust any proxy that forwards headers. This is generally safe if your application is only accessible via a trusted proxy (Nginx) and the network configuration prevents direct client access to Gunicorn.
If your infrastructure ensures that all incoming requests pass through Nginx, then using *
is acceptable because you rely on the proxy to sanitize and manage the headers. However, if there’s any risk that clients could bypass your reverse proxy and access Gunicorn directly, trusting all forwarded IPs could allow attackers to spoof headers such as X-Forwarded-For, potentially interfering with logging or other logic that depends on the client’s IP address.
For production, it’s best practice to restrict trusted proxies to known IP ranges if possible. In containerized environments, internal networking or firewall rules ensure that Gunicorn is only reachable from the proxy, which makes using *
is a safe option.
3. Verification
After making these changes, time to push to the staging enviroment where the stack runs locally:
- Restarted Docker containers to apply the new configurations.
- Tested the webhook endpoint by sending test events from Stripe.
- Logging was added to Django view to confirm that the
Stripe-Signature
header was present and correctly forwarded.
Once verified, the webhook signature could be successfully validated by Stripe’s library and the error was resolved without needing monkey patches.
Conclusion
The issue was ultimately caused by Nginx not forwarding headers containing underscores. By explicitly setting proxy_set_header Stripe-Signature $http_stripe_signature;
in our Nginx configuration and ensuring Gunicorn accepted forwarded headers, we restored the expected behavior. This approach is generally preferable in production environments as it maintains the integrity of the original library code while properly addressing the environment-specific header handling.
This experience underscores the importance of verifying header transmission in a multi-tiered deployment, especially when dealing with external services like Stripe that rely on header-based security.
Enjoyed This Article?
Unlock more with 3ee Games SpaceLab — your key to gaming greatness!
- Boost Your Game
Play games → earn coins → craft unique avatars in Britelite. No pay-to-win — just pure fun.
- Build Like a Pro
Game dev tutorials, API deep-dives, and AI training frameworks — code smarter, not harder.
- Rewards Just for You
Exclusive badges, quests, & surprises — because you deserve it.