Stripe Webhook Setup Guide
Overview
Stripe webhooks are essential for keeping your database in sync with payment and subscription events. When a payment succeeds, a subscription changes, or an invoice is paid, Stripe sends real-time notifications to your server.
Webhook Endpoint: /api/billing/webhooks/stripe/
Why Webhooks Are Important
Webhooks ensure that:
- ✅ Subscriptions are activated immediately after payment
- ✅ Failed payments update subscription status correctly
- ✅ Plan changes are reflected in your database
- ✅ Credits are added after successful purchases
- ✅ Cancellations are processed automatically
Without webhooks, your database would be out of sync with Stripe's records.
Handled Webhook Events
The system handles the following Stripe events:
Payment Events
payment_intent.succeeded- Payment completed successfullypayment_intent.failed- Payment failed (card declined, etc.)
Subscription Events
customer.subscription.created- New subscription createdcustomer.subscription.updated- Subscription plan or status changedcustomer.subscription.deleted- Subscription cancelled
Invoice Events
invoice.payment_succeeded- Invoice paid successfully, renews subscriptioninvoice.payment_failed- Invoice payment failed, marks subscription as past dueinvoice.finalized- Invoice generated and ready to be paid
Checkout Events
checkout.session.completed- Checkout completed, activates subscription
Setup Instructions
Step 1: Get Your Stripe Keys
- Go to Stripe Dashboard
- Copy your Secret key (starts with
sk_test_orsk_live_) - Copy your Publishable key (starts with
pk_test_orpk_live_) - Add them to your
.envfile:
Step 2: Configure Webhook Endpoint
For Production:
- Go to Stripe Dashboard → Webhooks
- Click "Add endpoint"
- Enter your endpoint URL:
- Click "Select events" and choose:
checkout.session.completedcustomer.subscription.createdcustomer.subscription.updatedcustomer.subscription.deletedinvoice.payment_succeededinvoice.payment_failedinvoice.finalizedpayment_intent.succeeded-
payment_intent.failed -
Click "Add endpoint"
- Copy the Signing secret (starts with
whsec_) - Add it to your
.envfile:
For Local Development:
Use the Stripe CLI to forward webhooks to your local server:
# Install Stripe CLI (macOS)
brew install stripe/stripe-cli/stripe
# Or download from: https://stripe.com/docs/stripe-cli
# Login to your Stripe account
stripe login
# Forward webhooks to local server
stripe listen --forward-to localhost:8000/api/billing/webhooks/stripe/
# Output will show your webhook signing secret
# Add it to .env as STRIPE_WEBHOOK_SECRET
The Stripe CLI will display the webhook secret. Add it to your .env:
Step 3: Verify Setup
Test that webhooks are working:
# With Stripe CLI running, trigger a test event
stripe trigger payment_intent.succeeded
# Check your server logs for:
# INFO: Received Stripe webhook: payment_intent.succeeded
Environment Variables
Your .env file should contain:
# Stripe Configuration
STRIPE_SECRET_KEY=sk_test_51xxxxx...
STRIPE_PUBLISHABLE_KEY=pk_test_51xxxxx...
STRIPE_WEBHOOK_SECRET=whsec_xxxxx...
Test vs Live Mode
- Test Mode: Use
sk_test_*andpk_test_*keys during development - Live Mode: Use
sk_live_*andpk_live_*keys in production - Each mode has its own webhook endpoints and signing secrets
Security
Signature Verification
All webhooks are verified using Stripe's signature verification:
- Stripe signs each webhook with your webhook secret
- Your server verifies the signature before processing
- Fake/tampered webhooks are rejected with 400 error
This prevents malicious actors from sending fake payment events.
CSRF Exemption
The webhook endpoint is exempt from CSRF protection because: - Stripe cannot send CSRF tokens - Signature verification provides better security - The endpoint only accepts POST requests
Webhook Processing
When a webhook is received:
1. Stripe sends POST to /api/billing/webhooks/stripe/
2. Server verifies Stripe signature
3. Server processes event based on type
4. Server updates database (subscriptions, invoices, credits)
5. Server returns 200 OK to acknowledge receipt
Important: If your server returns anything other than 200, Stripe will retry the webhook automatically.
Testing Webhooks
Test with Stripe CLI
# Make sure Stripe CLI is listening
stripe listen --forward-to localhost:8000/api/billing/webhooks/stripe/
# In another terminal, trigger test events:
# Test successful payment
stripe trigger payment_intent.succeeded
# Test subscription creation
stripe trigger customer.subscription.created
# Test invoice payment
stripe trigger invoice.payment_succeeded
# Test payment failure
stripe trigger payment_intent.failed
Test Cards (in Stripe Checkout)
Use these test card numbers in Stripe test mode:
- Success:
4242 4242 4242 4242 - Decline:
4000 0000 0000 0002 - Requires 3D Secure:
4000 0025 0000 3155 - Insufficient Funds:
4000 0000 0000 9995
Use any future expiry date and any 3-digit CVC.
Monitoring Webhooks
Stripe Dashboard
View webhook events and delivery status:
- Go to Stripe Dashboard → Webhooks
- Click on your endpoint
- View recent events and responses
- Check for failed deliveries (non-200 responses)
Application Logs
Check your server logs for webhook processing:
# Docker
docker compose logs -f web | grep webhook
# Standard
tail -f /var/log/app.log | grep webhook
Look for:
- ✅ INFO: Received Stripe webhook: <event_type>
- ✅ INFO: Updated subscription <id> to ACTIVE
- ❌ ERROR: Error handling webhook event
Troubleshooting
Webhook Events Not Received
Problem: Stripe sends webhooks but they're not reaching your server
Solutions: 1. Verify endpoint URL is correct and publicly accessible 2. Check firewall/security group allows inbound HTTPS traffic 3. Ensure your server is running and responding 4. Check webhook logs in Stripe Dashboard for errors
Signature Verification Fails
Problem: All webhooks return 400 error
Solutions:
1. Verify STRIPE_WEBHOOK_SECRET matches Stripe Dashboard
2. Check you're using the secret for the correct endpoint
3. Ensure you're not modifying the request body before verification
4. For local development, use the secret from stripe listen output
Webhooks Received but Not Processing
Problem: Webhooks return 200 but database not updating
Solutions: 1. Check server logs for errors during processing 2. Verify database connection is working 3. Check that Stripe IDs match (customer_id, subscription_id) 4. Ensure subscription/customer records exist in database
Duplicate Events
Problem: Same event processed multiple times
Note: This is normal behavior. Stripe may send the same event multiple times for reliability. Your webhook handlers are designed to be idempotent (safe to process multiple times).
Webhook Retry Logic
Stripe automatically retries failed webhooks:
- Initial attempt: Immediate
- Retry 1: After 5 minutes
- Retry 2: After 30 minutes
- Retry 3: After 2 hours
- Retry 4: After 4 hours
- Retry 5: After 8 hours
After all retries fail, the event is marked as failed in the dashboard.
Best Practice: Fix issues quickly so retries succeed.
What Happens During Each Event
checkout.session.completed
- Finds subscription by customer ID
- Links Stripe subscription ID
- Activates subscription
- Allocates monthly credits
customer.subscription.updated
- Updates subscription status (active, past_due, cancelled)
- Updates billing period dates
- Reflects plan changes
invoice.payment_succeeded
- Marks invoice as paid
- Renews subscription credits
- Extends subscription period
invoice.payment_failed
- Marks invoice as uncollectible
- Sets subscription to past_due
- (TODO: Send email notification)
payment_intent.succeeded
- Updates transaction status
- For credit purchases: Adds credits to workspace
payment_intent.failed
- Updates transaction status to failed
- Records error message
Development Workflow
Standard Development Flow
# Terminal 1: Start your Django server
docker compose up
# Terminal 2: Forward Stripe webhooks
stripe listen --forward-to localhost:8000/api/billing/webhooks/stripe/
# Terminal 3: Trigger test events
stripe trigger checkout.session.completed
stripe trigger invoice.payment_succeeded
Testing a Complete Subscription Flow
- Create a test subscription via Checkout
- Use test card
4242 4242 4242 4242 - Complete payment
- Watch webhook logs for:
checkout.session.completedcustomer.subscription.createdinvoice.payment_succeeded- Verify subscription is active in database
Production Checklist
Before going live:
- [ ] Switch to live Stripe keys (
sk_live_*,pk_live_*) - [ ] Create production webhook endpoint in Stripe Dashboard
- [ ] Add production webhook secret to
.env - [ ] Test webhook endpoint is publicly accessible
- [ ] Monitor webhook delivery in Stripe Dashboard
- [ ] Set up alerts for failed webhooks
- [ ] Configure Customer Portal in Stripe Dashboard
- [ ] Test end-to-end subscription flow with live mode test