How to Receive Webhooks from Stripe
Introduction
Stripe is one of the most popular payment processors, and receiving webhooks from Stripe is essential for handling payment events in real-time. This guide will walk you through setting up Core Webhook Module to receive and process Stripe webhooks securely.
Understanding Stripe Webhooks
Stripe sends webhook events to notify your application about important events in your Stripe account, such as:
- Payment successes and failures
- Subscription updates
- Chargebacks and disputes
- Invoice payments
- Customer updates
- And many more...
Stripe Webhook Security
Stripe uses HMAC signature verification to ensure webhook requests are authentic. Each webhook request includes:
- X-Stripe-Signature header: Contains a timestamp and signature in the format
t=<timestamp>,v1=<signature> - Signing Secret: A secret key unique to each webhook endpoint (starts with
whsec_) - HMAC-SHA256: The signature is computed using HMAC-SHA256 over the timestamp and request body
Important: Always verify webhook signatures to prevent unauthorized requests from being processed.
Prerequisites
Before you begin, ensure you have:
- Core Webhook Module installed and running
- A Stripe account (test or live)
- Access to your Stripe Dashboard
- (Optional) ngrok or similar tool for local development
Step-by-Step Setup
Step 1: Configure Core Webhook Module
First, let's configure Core Webhook Module to receive Stripe webhooks. You'll need to set up:
- Webhook Configuration (
webhooks.json) - Connection Configuration (
connections.json) - if storing in a database - Environment Variables - for sensitive data
Basic Stripe Webhook Configuration
Create or update your webhooks.json file:
{
"stripe_payments": {
"data_type": "json",
"module": "log",
"hmac": {
"secret": "{$STRIPE_WEBHOOK_SECRET}",
"header": "X-Stripe-Signature",
"algorithm": "sha256"
},
"rate_limit": {
"max_requests": 100,
"window_seconds": 60
}
}
}
Configuration Explanation:
stripe_payments: Your webhook ID (used in the URL:/webhook/stripe_payments)data_type:"json"for JSON payloadsmodule: Where to send the webhook data (we'll explore options below)hmac: HMAC signature validation configurationsecret: Your Stripe webhook signing secret (starts withwhsec_)header: Stripe's signature header name (X-Stripe-Signature)algorithm:sha256(Stripe uses HMAC-SHA256)
rate_limit: Optional rate limiting to prevent abuse
Using Environment Variables
For security, use environment variables for sensitive data. Create a .env file:
STRIPE_WEBHOOK_SECRET=whsec_your_webhook_signing_secret_here
Then reference it in your configuration using {$STRIPE_WEBHOOK_SECRET}.
Step 2: Get Your Stripe Webhook Signing Secret
-
Log in to Stripe Dashboard: Go to https://dashboard.stripe.com
-
Navigate to Webhooks:
- Click on Developers in the left sidebar
- Click on Webhooks
-
Create or Select Endpoint:
- If you haven't created an endpoint yet, click Add endpoint
- Enter your webhook URL (we'll set this up in the next step)
- Select the events you want to receive
- Click Add endpoint
-
Get Signing Secret:
- Click on your webhook endpoint
- Under Signing secret, click Reveal
- Copy the secret (it starts with
whsec_)
-
Add to Environment Variables:
export STRIPE_WEBHOOK_SECRET=whsec_your_actual_secret_here
Step 3: Set Up Your Webhook URL
For Production
If you're deploying to production, use your production URL:
https://yourdomain.com/webhook/stripe_payments
For Local Development
For local development, you'll need to expose your local server to the internet. Here are two options:
Option A: Using ngrok (Recommended)
-
Install ngrok: Download from https://ngrok.com/download
-
Start Core Webhook Module:
uvicorn src.main:app --reload --host 0.0.0.0 --port 8000 -
Start ngrok:
ngrok http 8000 -
Copy the HTTPS URL: ngrok will provide a URL like
https://abc123.ngrok.io -
Use in Stripe Dashboard:
- Webhook URL:
https://abc123.ngrok.io/webhook/stripe_payments
- Webhook URL:
Option B: Using Stripe CLI (Alternative)
Stripe CLI can forward webhooks to your local server:
-
Install Stripe CLI: Follow Stripe CLI installation guide
-
Login to Stripe:
stripe login -
Forward webhooks:
stripe listen --forward-to localhost:8000/webhook/stripe_payments -
Get webhook signing secret:
stripe listen --print-secret
Step 4: Configure Where to Send Webhook Data
Core Webhook Module supports multiple destinations. Here are common configurations:
Option 1: Log to Console (Development)
{
"stripe_payments": {
"data_type": "json",
"module": "log",
"hmac": {
"secret": "{$STRIPE_WEBHOOK_SECRET}",
"header": "X-Stripe-Signature",
"algorithm": "sha256"
}
}
}
Option 2: Save to Database (PostgreSQL)
{
"stripe_payments": {
"data_type": "json",
"module": "postgresql",
"connection": "postgres_prod",
"module-config": {
"table": "stripe_events",
"storage_mode": "json"
},
"hmac": {
"secret": "{$STRIPE_WEBHOOK_SECRET}",
"header": "X-Stripe-Signature",
"algorithm": "sha256"
}
}
}
Connection Configuration (connections.json):
{
"postgres_prod": {
"type": "postgresql",
"host": "{$POSTGRES_HOST}",
"port": 5432,
"database": "{$POSTGRES_DB}",
"user": "{$POSTGRES_USER}",
"password": "{$POSTGRES_PASSWORD}"
}
}
Option 3: Send to Message Queue (RabbitMQ)
{
"stripe_payments": {
"data_type": "json",
"module": "rabbitmq",
"connection": "rabbitmq_prod",
"module-config": {
"queue_name": "stripe_events"
},
"hmac": {
"secret": "{$STRIPE_WEBHOOK_SECRET}",
"header": "X-Stripe-Signature",
"algorithm": "sha256"
}
}
}
Option 4: Store in S3 (Archival)
{
"stripe_payments": {
"data_type": "json",
"module": "s3",
"connection": "s3_storage",
"module-config": {
"bucket": "webhook-archive",
"prefix": "stripe/events",
"filename_pattern": "stripe_{timestamp}_{uuid}.json"
},
"hmac": {
"secret": "{$STRIPE_WEBHOOK_SECRET}",
"header": "X-Stripe-Signature",
"algorithm": "sha256"
}
}
}
Option 5: Chain Multiple Destinations
You can send to multiple destinations using webhook chaining:
{
"stripe_payments": {
"data_type": "json",
"chain": [
{
"module": "postgresql",
"connection": "postgres_prod",
"module-config": {
"table": "stripe_events"
}
},
{
"module": "rabbitmq",
"connection": "rabbitmq_prod",
"module-config": {
"queue_name": "stripe_events"
}
}
],
"chain-config": {
"execution": "parallel",
"continue_on_error": true
},
"hmac": {
"secret": "{$STRIPE_WEBHOOK_SECRET}",
"header": "X-Stripe-Signature",
"algorithm": "sha256"
}
}
}
Step 5: Start Core Webhook Module
# Install dependencies (if not already done)
pip install -r requirements.txt
# Start the server
uvicorn src.main:app --reload --host 0.0.0.0 --port 8000
The server will be available at:
- API:
http://localhost:8000 - Webhook endpoint:
http://localhost:8000/webhook/stripe_payments - API Documentation:
http://localhost:8000/docs
Step 6: Test Your Webhook
Using Stripe Dashboard
- Go to Stripe Dashboard > Developers > Webhooks
- Click on your webhook endpoint
- Click "Send test webhook"
- Select an event type (e.g.,
payment_intent.succeeded) - Click "Send test webhook"
Using Stripe CLI
# Trigger a test event
stripe trigger payment_intent.succeeded
# Or forward events to your local server
stripe listen --forward-to localhost:8000/webhook/stripe_payments
Verify It's Working
Check your Core Webhook Module logs. You should see:
- Successful HMAC signature validation
- Webhook payload being processed
- Data being sent to your configured destination
Understanding Stripe Webhook Events
Common Stripe Events
Here are some common Stripe events you might want to handle:
payment_intent.succeeded- Payment completed successfullypayment_intent.payment_failed- Payment failedcharge.succeeded- Charge succeededcharge.failed- Charge failedcustomer.subscription.created- New subscription createdcustomer.subscription.updated- Subscription updatedcustomer.subscription.deleted- Subscription cancelledinvoice.payment_succeeded- Invoice paidinvoice.payment_failed- Invoice payment failedcharge.dispute.created- Chargeback/dispute created
Event Payload Structure
Stripe webhook payloads follow this structure:
{
"id": "evt_1234567890",
"object": "event",
"api_version": "2023-10-16",
"created": 1699123456,
"data": {
"object": {
// Event-specific data (e.g., PaymentIntent, Charge, etc.)
},
"previous_attributes": {
// Fields that changed (for update events)
}
},
"livemode": false,
"pending_webhooks": 1,
"request": {
"id": "req_1234567890",
"idempotency_key": null
},
"type": "payment_intent.succeeded"
}
Advanced Configuration
Adding Rate Limiting
Protect your endpoint from abuse:
{
"stripe_payments": {
"data_type": "json",
"module": "log",
"hmac": {
"secret": "{$STRIPE_WEBHOOK_SECRET}",
"header": "X-Stripe-Signature",
"algorithm": "sha256"
},
"rate_limit": {
"max_requests": 100,
"window_seconds": 60
}
}
}
Adding IP Whitelisting
Restrict access to Stripe's IP addresses (optional, as HMAC validation is usually sufficient):
{
"stripe_payments": {
"data_type": "json",
"module": "log",
"hmac": {
"secret": "{$STRIPE_WEBHOOK_SECRET}",
"header": "X-Stripe-Signature",
"algorithm": "sha256"
},
"ip_whitelist": [
"54.187.174.169",
"54.187.205.235",
"54.187.216.72"
// Add more Stripe IPs as needed
]
}
}
Note: Stripe's IP addresses can change. HMAC signature validation is the recommended security method.
Adding Retry Logic
Handle temporary failures with automatic retries:
{
"stripe_payments": {
"data_type": "json",
"module": "postgresql",
"connection": "postgres_prod",
"module-config": {
"table": "stripe_events"
},
"hmac": {
"secret": "{$STRIPE_WEBHOOK_SECRET}",
"header": "X-Stripe-Signature",
"algorithm": "sha256"
},
"retry": {
"enabled": true,
"max_attempts": 3,
"initial_delay": 1.0,
"max_delay": 60.0,
"backoff_multiplier": 2.0
}
}
}
Credential Cleanup
Automatically mask sensitive data before logging:
{
"stripe_payments": {
"data_type": "json",
"module": "log",
"hmac": {
"secret": "{$STRIPE_WEBHOOK_SECRET}",
"header": "X-Stripe-Signature",
"algorithm": "sha256"
},
"credential_cleanup": {
"enabled": true,
"mode": "mask",
"fields": ["secret", "api_key", "private_key"]
}
}
}
Troubleshooting
Issue: HMAC Signature Validation Failing
Symptoms: Webhook requests are rejected with "Invalid HMAC signature"
Solutions:
- Verify your signing secret: Make sure you're using the correct secret from Stripe Dashboard
- Check header name: Ensure
headeris set to"X-Stripe-Signature"(case-sensitive) - Verify algorithm: Must be
"sha256" - Check environment variable: Ensure
{$STRIPE_WEBHOOK_SECRET}is properly set - Raw body: Make sure Core Webhook Module is receiving the raw request body (it does by default)
Issue: Webhooks Not Being Received
Symptoms: No webhook events appearing in logs
Solutions:
- Check webhook URL: Verify the URL in Stripe Dashboard matches your endpoint
- Check server status: Ensure Core Webhook Module is running
- Check ngrok: If using ngrok, verify it's running and forwarding correctly
- Check firewall: Ensure port 8000 (or your port) is accessible
- Check Stripe Dashboard: Look for failed webhook deliveries in Stripe Dashboard
Issue: Webhook Events Not Processing
Symptoms: Webhooks received but not processed correctly
Solutions:
- Check module configuration: Verify your module (log, postgresql, etc.) is configured correctly
- Check connection: If using a database or message queue, verify connection details
- Check logs: Look for error messages in Core Webhook Module logs
- Test module separately: Try sending a test request directly to verify module functionality
Security Best Practices
- Always Verify HMAC Signatures: Never skip signature verification in production
- Use Environment Variables: Never hardcode secrets in configuration files
- Use HTTPS: Always use HTTPS in production (Stripe requires it)
- Monitor Webhook Failures: Set up alerts for failed webhook deliveries
- Idempotency: Handle duplicate events (Stripe may retry failed webhooks)
- Rate Limiting: Use rate limiting to prevent abuse
- Credential Cleanup: Enable credential cleanup to prevent sensitive data exposure in logs
Complete Example Configuration
Here's a complete production-ready configuration:
webhooks.json:
{
"stripe_payments": {
"data_type": "json",
"module": "postgresql",
"connection": "postgres_prod",
"module-config": {
"table": "stripe_events",
"storage_mode": "json",
"include_headers": true
},
"hmac": {
"secret": "{$STRIPE_WEBHOOK_SECRET}",
"header": "X-Stripe-Signature",
"algorithm": "sha256"
},
"rate_limit": {
"max_requests": 200,
"window_seconds": 60
},
"retry": {
"enabled": true,
"max_attempts": 3,
"initial_delay": 1.0,
"max_delay": 60.0
},
"credential_cleanup": {
"enabled": true,
"mode": "mask"
}
}
}
connections.json:
{
"postgres_prod": {
"type": "postgresql",
"host": "{$POSTGRES_HOST}",
"port": 5432,
"database": "{$POSTGRES_DB}",
"user": "{$POSTGRES_USER}",
"password": "{$POSTGRES_PASSWORD}"
}
}
.env:
STRIPE_WEBHOOK_SECRET=whsec_your_actual_secret_here
POSTGRES_HOST=localhost
POSTGRES_DB=webhook_db
POSTGRES_USER=webhook_user
POSTGRES_PASSWORD=your_secure_password
Next Steps
- Set up monitoring: Monitor webhook delivery success rates
- Handle events: Implement business logic to process different event types
- Set up alerts: Configure alerts for critical events (payment failures, chargebacks)
- Test thoroughly: Test all event types you'll be handling
- Document your events: Document which events your application handles
Additional Resources
Conclusion
You now have Core Webhook Module configured to securely receive and process Stripe webhooks! The HMAC signature validation ensures that only authentic requests from Stripe are processed, and the flexible routing system allows you to send webhook data to any destination you need.
Remember to:
- Always verify HMAC signatures
- Use environment variables for secrets
- Test thoroughly before going to production
- Monitor webhook delivery success rates
Happy webhook processing! 🎉
This is part 2 of our blog series. Check out Part 1: What is This Tool? for an overview of Core Webhook Module.