Stripe Webhook Setup Guide
Date: 2025-01-04 Status: ✅ Code Complete - Ready for Configuration
Overview
This guide walks through setting up Stripe webhooks to sync subscription events to your Convex database. The webhook handler code is complete and ready to receive events.
Files Created
1. Convex Webhook Handlers
File: convex/stripe/webhooks.ts
Contains internal mutations that handle Stripe events:
handleSubscriptionCreated- Creates/updates subscription in ConvexhandleSubscriptionUpdated- Updates tier, status, billing periodhandleSubscriptionDeleted- Downgrades to free tierhandlePaymentSucceeded- Confirms successful paymentshandlePaymentFailed- Marks subscription as past_due
2. Next.js API Route
File: app/api/stripe/webhook/route.ts
POST endpoint that:
- Receives webhooks from Stripe
- Verifies webhook signatures
- Extracts tier from price IDs
- Forwards events to Convex mutations
Endpoint: POST /api/stripe/webhook
Environment Variables Required
Add these to your .env.local file:
# Stripe Keys
STRIPE_SECRET_KEY=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_... # You'll get this after creating the webhook
# Price IDs (from STRIPE_PRODUCT_SETUP.md)
STRIPE_PERSONAL_MONTHLY_PRICE_ID=price_1SPjo4Aye5brjRffe1sdUwS9
STRIPE_PERSONAL_YEARLY_PRICE_ID=price_1SPjoGAye5brjRff3OxcvQo8
STRIPE_PRO_MONTHLY_PRICE_ID=price_1SPjokAye5brjRffO0iaBf5S
STRIPE_PRO_YEARLY_PRICE_ID=price_1SPjovAye5brjRfftU1yWaqo
STRIPE_TEAM_MONTHLY_PRICE_ID=price_1SPjqmAye5brjRffVAyS9mhe
STRIPE_TEAM_YEARLY_PRICE_ID=price_1SPjqwAye5brjRffqf7Enyuv
# Convex (should already exist)
NEXT_PUBLIC_CONVEX_URL=https://your-project.convex.cloudSetup Steps
Step 1: Create ngrok Tunnel (Development)
For local development, create a tunnel to expose your local server:
# Start your Next.js dev server first
pnpm dev
# In another terminal, create tunnel
ngrok http 3017You'll get a URL like: https://abc123.ngrok.io
Your webhook URL will be: https://abc123.ngrok.io/api/stripe/webhook
Step 2: Configure Webhook in Stripe Dashboard
- Go to Stripe Dashboard → Developers → Webhooks
- Click Add endpoint
- Enter your webhook URL:
- Development:
https://your-ngrok-url.ngrok.io/api/stripe/webhook - Production:
https://homepage.dev/api/stripe/webhook
- Development:
- Select events to send:
customer.subscription.createdcustomer.subscription.updatedcustomer.subscription.deletedinvoice.payment_succeededinvoice.payment_failed
- Click Add endpoint
- Click Reveal on the signing secret and copy it
- Add the signing secret to your
.env.localasSTRIPE_WEBHOOK_SECRET
Step 3: Restart Your Dev Server
After adding the webhook secret to .env.local:
# Stop your dev server (Ctrl+C)
# Restart it to load new environment variables
pnpm devStep 4: Test Webhook
Option A: Using Stripe Dashboard
- Go to Stripe Dashboard → Developers → Webhooks
- Click on your webhook endpoint
- Click Send test webhook
- Select an event type (e.g.,
customer.subscription.created) - Click Send test webhook
- Check your Next.js console for logs
Option B: Using Stripe CLI (Recommended)
Install Stripe CLI if you haven't:
brew install stripe/stripe-cli/stripe
stripe loginForward events to your local webhook:
stripe listen --forward-to localhost:3017/api/stripe/webhookTrigger test events:
# Test subscription created
stripe trigger customer.subscription.created
# Test subscription updated
stripe trigger customer.subscription.updated
# Test payment succeeded
stripe trigger invoice.payment_succeeded
# Test payment failed
stripe trigger invoice.payment_failedEvent Flow
1. Subscription Created
User completes checkout
→ Stripe creates subscription
→ Webhook: customer.subscription.created
→ API verifies signature
→ Extracts tier from price ID
→ Convex: handleSubscriptionCreated
→ Subscription record created/updated in DB2. Subscription Updated
User changes plan or billing cycle
→ Stripe updates subscription
→ Webhook: customer.subscription.updated
→ API verifies signature
→ Convex: handleSubscriptionUpdated
→ Tier and dates updated in DB3. Payment Succeeded
Stripe processes payment
→ Webhook: invoice.payment_succeeded
→ API verifies signature
→ Convex: handlePaymentSucceeded
→ Status set to "active", period extended4. Payment Failed
Payment fails (card declined, etc.)
→ Webhook: invoice.payment_failed
→ API verifies signature
→ Convex: handlePaymentFailed
→ Status set to "past_due"5. Subscription Canceled
User cancels subscription
→ Stripe cancels at period end or immediately
→ Webhook: customer.subscription.deleted
→ API verifies signature
→ Convex: handleSubscriptionDeleted
→ Tier downgraded to "free", status set to "canceled"Webhook Security
The webhook handler includes several security measures:
- Signature Verification: Every webhook is verified using Stripe's signature
- Environment Validation: Checks all required environment variables at startup
- Error Handling: Proper error responses and logging
- Metadata Validation: Ensures userId exists in subscription metadata
Troubleshooting
Webhook Not Receiving Events
Check:
- Is your ngrok tunnel active? (
ngrok http 3017) - Is your Next.js dev server running? (
pnpm dev) - Is the webhook URL correct in Stripe dashboard?
- Are you in the correct Stripe mode (test/live)?
Signature Verification Failed
Check:
- Is
STRIPE_WEBHOOK_SECRETset correctly in.env.local? - Did you restart the dev server after adding the secret?
- Are you using the correct signing secret (test vs live)?
"No userId in metadata" Error
Cause: Subscription created without userId in metadata
Fix: Ensure checkout session includes userId in metadata (will be implemented in checkout flow)
Unknown Price ID
Check:
- Are all price ID environment variables set?
- Do they match the IDs from
STRIPE_PRODUCT_SETUP.md? - Did you restart dev server after adding them?
Console Logs
The webhook handler logs events for debugging:
Received webhook: customer.subscription.created
Subscription created: sub_1234567890 for user user_abc123Received webhook: customer.subscription.updated
Subscription updated: sub_1234567890Received webhook: invoice.payment_succeeded
Payment succeeded for subscription: sub_1234567890Production Deployment
Vercel/Production Setup
- Add all environment variables to your hosting platform
- Update webhook URL in Stripe to production domain:
https://homepage.dev/api/stripe/webhook
- Use live mode keys and webhook secret
- Monitor webhook delivery in Stripe dashboard
Webhook Monitoring
Stripe Dashboard → Developers → Webhooks → [Your Endpoint]
Monitor:
- Success rate: Should be near 100%
- Recent deliveries: Check for failures
- Retry attempts: Stripe retries failed webhooks
Next Steps
After webhook setup is complete:
- ✅ Webhook handler code created
- ⏳ Create checkout flow (
app/api/stripe/checkout/route.ts) - ⏳ Create customer portal (
app/api/stripe/portal/route.ts) - ⏳ Add frontend upgrade buttons
- ⏳ Test end-to-end subscription flow
Reference Documents:
docs/STRIPE_PRODUCT_SETUP.md- Product and price IDsdocs/PHASE_1_IMPLEMENTATION_COMPLETE.md- Backend tier enforcementdocs/TIER_MANAGEMENT_PLAN.md- Complete implementation roadmap