Skip to main content

Stripe Webhook Testing Guide

Complete guide for testing Stripe webhooks locally before production deployment.

Prerequisites

  1. Stripe Account (free test mode account)

  2. Stripe CLI (for local webhook testing)

  3. 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.status set to "canceled"
  • billing.stripeSubscriptionId set 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.status updated to "active"
  • billing.lastPaymentDate set
  • billing.lastPaymentAmount set

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.status updated to "past_due"
  • billing.lastPaymentAttempt set
  • 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:

  1. Check STRIPE_WEBHOOK_SECRET is set correctly
  2. Restart producer-service after updating .env
  3. Ensure Stripe CLI is forwarding to correct URL
  4. 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:

  1. Verify organization has billing.stripeCustomerId field
  2. Check customer ID matches exactly
  3. 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:

  1. Is Stripe CLI running? (stripe listen)
  2. Is producer-service accessible? (curl http://localhost:3000/api/webhooks/test)
  3. Is webhook secret configured? (Check logs: "webhookSecretConfigured": true)
  4. 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:

  1. Go to: https://dashboard.stripe.com/webhooks
  2. Click "Add endpoint"
  3. Enter URL: https://your-domain.com/api/webhooks/stripe
  4. Select events:
    • customer.subscription.created
    • customer.subscription.updated
    • customer.subscription.deleted
    • invoice.payment_succeeded
    • invoice.payment_failed
  5. Copy the webhook signing secret
  6. 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: