Stripe Webhook Testing Guide
Complete guide for testing Stripe webhooks locally before production deployment.
Prerequisites
-
Stripe Account (free test mode account)
- Sign up at: https://dashboard.stripe.com/register
- Get your test API keys from: https://dashboard.stripe.com/test/apikeys
-
Stripe CLI (for local webhook testing)
- Installation: https://docs.stripe.com/stripe-cli
- Windows:
scoop install stripe - Mac:
brew install stripe/stripe-cli/stripe - Linux: Download from releases page
-
Environment Variables
# In .env file
STRIPE_SECRET_KEY=sk_test_...
STRIPE_PUBLISHABLE_KEY=pk_test_...
STRIPE_WEBHOOK_SECRET=whsec_... # Generated by Stripe CLI
STRIPE_TEAM_PRICE_ID=price_...
STRIPE_ENTERPRISE_PRICE_ID=price_...
Setup Instructions
Step 1: Create Stripe Products and Prices
# Login to Stripe CLI
stripe login
# Create Team plan product
stripe products create \
--name="Team Plan" \
--description="Up to 20 users, 1000 test runs/month"
# Create Team plan price
stripe prices create \
--product=prod_xxxxx \
--unit-amount=9900 \
--currency=usd \
--recurring[interval]=month
# Create Enterprise plan product
stripe products create \
--name="Enterprise Plan" \
--description="Unlimited users and test runs"
# Create Enterprise plan price
stripe prices create \
--product=prod_xxxxx \
--unit-amount=49900 \
--currency=usd \
--recurring[interval]=month
Copy the price IDs (they start with price_) and add them to your .env file:
STRIPE_TEAM_PRICE_ID=price_1234567890abcdef
STRIPE_ENTERPRISE_PRICE_ID=price_0987654321fedcba
Step 2: Start the Producer Service
# Start Docker services
docker-compose up --build
# Verify producer-service is running
docker-compose logs -f producer-service
# Test webhook endpoint is accessible
curl http://localhost:3000/api/webhooks/test
Expected Response:
{
"status": "ok",
"message": "Webhook endpoint is accessible",
"stripeConfigured": true,
"webhookSecretConfigured": false
}
Step 3: Start Stripe CLI Webhook Forwarding
# Forward webhooks to local endpoint
stripe listen --forward-to http://localhost:3000/api/webhooks/stripe
# You should see output like:
# > Ready! Your webhook signing secret is whsec_1234567890abcdef
Copy the webhook signing secret and add it to your .env file:
STRIPE_WEBHOOK_SECRET=whsec_1234567890abcdef
Restart the producer service to load the new secret:
docker-compose restart producer-service
Testing Webhook Events
Test 1: Subscription Created
# Trigger subscription.created event
stripe trigger customer.subscription.created
# Check producer-service logs
docker-compose logs producer-service | grep "Webhook"
Expected Log Output:
✅ Webhook verified: customer.subscription.created (evt_xxxxx)
✅ Subscription created: org=123, plan=team, status=active
Verify in MongoDB:
docker exec -it agnostic-automation-center-mongodb-1 mongosh
use automation_platform
# Check webhook logs
db.webhook_logs.find({eventType: "customer.subscription.created"}).pretty()
# Check organization was updated
db.organizations.findOne({_id: ObjectId("YOUR_ORG_ID")})
# Should see:
# - billing.stripeSubscriptionId
# - billing.status: "active"
# - plan: "team" or "enterprise"
Test 2: Subscription Updated
# Trigger subscription.updated event (plan change)
stripe trigger customer.subscription.updated
# Check logs
docker-compose logs producer-service | grep "Subscription updated"
Expected Behavior:
- Organization plan and limits updated
- Webhook log entry created
- No errors in logs
Test 3: Subscription Deleted (Cancellation)
# Trigger subscription.deleted event
stripe trigger customer.subscription.deleted
# Check logs
docker-compose logs producer-service | grep "Subscription canceled"
Expected Behavior:
- Organization downgraded to free plan
billing.statusset to "canceled"billing.stripeSubscriptionIdset to null- Limits reset to free tier
Verify:
db.organizations.findOne({_id: ObjectId("YOUR_ORG_ID")})
// Should see:
// - plan: "free"
// - limits.maxTestRuns: 100
// - limits.maxUsers: 3
// - billing.status: "canceled"
Test 4: Payment Succeeded
# Trigger invoice.payment_succeeded event
stripe trigger invoice.payment_succeeded
# Check logs
docker-compose logs producer-service | grep "Payment succeeded"
Expected Behavior:
billing.statusupdated to "active"billing.lastPaymentDatesetbilling.lastPaymentAmountset
Test 5: Payment Failed
# Trigger invoice.payment_failed event
stripe trigger invoice.payment_failed
# Check logs
docker-compose logs producer-service | grep "Payment failed"
Expected Behavior:
billing.statusupdated to "past_due"billing.lastPaymentAttemptset- Warning logged for manual review
Testing Full Subscription Flow
Scenario: User Upgrades from Free to Team Plan
Prerequisites:
- Organization exists in database with plan="free"
- User is authenticated and has admin role
Step 1: Create Stripe Customer
# In Stripe CLI, create a test customer
stripe customers create \
--email="admin@example.com" \
--description="Test Organization"
# Copy the customer ID (starts with cus_)
Step 2: Link Customer to Organization
// In MongoDB
db.organizations.updateOne(
{_id: ObjectId("YOUR_ORG_ID")},
{$set: {"billing.stripeCustomerId": "cus_xxxxx"}}
)
Step 3: Create Subscription via API
# Get JWT token
curl -X POST http://localhost:3000/api/auth/login \
-H "Content-Type: application/json" \
-d '{"email":"admin@example.com","password":"password"}'
# Copy JWT token, then create checkout session
curl -X POST http://localhost:3000/api/billing/checkout \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"priceId":"price_TEAM_PRICE_ID"}'
# Copy the checkout URL from response
Step 4: Complete Checkout
Open the checkout URL in browser and use test card:
- Card:
4242 4242 4242 4242 - Expiry: Any future date (e.g., 12/34)
- CVC: Any 3 digits (e.g., 123)
Step 5: Verify Webhook Processing
# Watch logs in real-time
docker-compose logs -f producer-service
# You should see:
# ✅ Webhook verified: customer.subscription.created
# ✅ Subscription created: org=..., plan=team, status=active
Step 6: Verify Organization Updated
db.organizations.findOne({_id: ObjectId("YOUR_ORG_ID")})
// Should see:
// - plan: "team"
// - limits.maxTestRuns: 1000
// - limits.maxUsers: 20
// - billing.stripeSubscriptionId: "sub_xxxxx"
// - billing.status: "active"
Debugging Webhook Issues
Issue: Webhook Signature Verification Failed
Symptom:
⚠️ Webhook signature verification failed: No signatures found matching the expected signature
Solutions:
- Check
STRIPE_WEBHOOK_SECRETis set correctly - Restart producer-service after updating
.env - Ensure Stripe CLI is forwarding to correct URL
- Check raw body parsing is enabled
Verify raw body:
# Check server.ts has rawBody plugin registered
grep "rawBody" apps/producer-service/src/config/server.ts
Issue: Organization Not Found
Symptom:
Organization not found for customer: cus_xxxxx
Solutions:
- Verify organization has
billing.stripeCustomerIdfield - Check customer ID matches exactly
- Ensure customer was created in correct Stripe account (test vs live)
Fix:
db.organizations.updateOne(
{_id: ObjectId("YOUR_ORG_ID")},
{$set: {"billing.stripeCustomerId": "cus_xxxxx"}}
)
Issue: Duplicate Webhook Processing
Symptom:
E11000 duplicate key error collection: webhook_logs.eventId
This is normal! Stripe may retry webhooks. The unique index prevents duplicate processing.
Issue: Webhook Not Received
Checklist:
- Is Stripe CLI running? (
stripe listen) - Is producer-service accessible? (
curl http://localhost:3000/api/webhooks/test) - Is webhook secret configured? (Check logs: "webhookSecretConfigured": true)
- Are you in correct Stripe account mode? (test vs live)
Monitoring Webhook Health
Check Recent Webhook Logs
// In MongoDB
use automation_platform
// Last 10 webhook events
db.webhook_logs.find()
.sort({processedAt: -1})
.limit(10)
.pretty()
// Failed webhooks
db.webhook_logs.find({status: "error"})
.sort({processedAt: -1})
.pretty()
// Webhooks for specific organization
db.webhook_logs.find({organizationId: "YOUR_ORG_ID"})
.sort({processedAt: -1})
.pretty()
Webhook Log Structure
{
_id: ObjectId("..."),
eventId: "evt_1234567890",
eventType: "customer.subscription.created",
organizationId: "507f1f77bcf86cd799439011",
status: "success",
error: null,
payload: {...}, // Full Stripe event object
createdAt: ISODate("2026-02-05T10:00:00.000Z"),
processedAt: ISODate("2026-02-05T10:00:01.000Z")
}
Production Webhook Setup
When deploying to production, configure webhooks in Stripe Dashboard:
- Go to: https://dashboard.stripe.com/webhooks
- Click "Add endpoint"
- Enter URL:
https://your-domain.com/api/webhooks/stripe - Select events:
customer.subscription.createdcustomer.subscription.updatedcustomer.subscription.deletedinvoice.payment_succeededinvoice.payment_failed
- Copy the webhook signing secret
- Add to production environment:
STRIPE_WEBHOOK_SECRET=whsec_prod_xxxxx
Important: Use separate webhook endpoints for test and live modes!
Troubleshooting Checklist
- Stripe CLI installed and authenticated
- Stripe products and prices created
- Price IDs added to
.env - Webhook secret added to
.env - Producer-service restarted after env changes
- Raw body parsing enabled in server.ts
- Webhook routes registered in routes.ts
- MongoDB indexes created (migration 004)
- Organization has Stripe customer ID
- Webhook test endpoint returns 200 OK
- Stripe CLI showing "Ready!" message
- Producer-service logs show no errors
Next Steps
After webhook testing is complete:
- Sprint 4: Build billing dashboard UI
- Sprint 5: End-to-end integration testing
- Production: Configure live webhooks in Stripe Dashboard
For questions or issues, check:
- Stripe Webhook Docs: https://docs.stripe.com/webhooks
- Stripe CLI Docs: https://docs.stripe.com/stripe-cli
- Project docs:
docs/implementation/phase-3/