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 Convex
  • handleSubscriptionUpdated - Updates tier, status, billing period
  • handleSubscriptionDeleted - Downgrades to free tier
  • handlePaymentSucceeded - Confirms successful payments
  • handlePaymentFailed - 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.cloud

Setup 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 3017

You'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

  1. Go to Stripe Dashboard → DevelopersWebhooks
  2. Click Add endpoint
  3. Enter your webhook URL:
    • Development: https://your-ngrok-url.ngrok.io/api/stripe/webhook
    • Production: https://homepage.dev/api/stripe/webhook
  4. Select events to send:
    • customer.subscription.created
    • customer.subscription.updated
    • customer.subscription.deleted
    • invoice.payment_succeeded
    • invoice.payment_failed
  5. Click Add endpoint
  6. Click Reveal on the signing secret and copy it
  7. Add the signing secret to your .env.local as STRIPE_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 dev

Step 4: Test Webhook

Option A: Using Stripe Dashboard

  1. Go to Stripe Dashboard → DevelopersWebhooks
  2. Click on your webhook endpoint
  3. Click Send test webhook
  4. Select an event type (e.g., customer.subscription.created)
  5. Click Send test webhook
  6. Check your Next.js console for logs

Install Stripe CLI if you haven't:

brew install stripe/stripe-cli/stripe
stripe login

Forward events to your local webhook:

stripe listen --forward-to localhost:3017/api/stripe/webhook

Trigger 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_failed

Event 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 DB

2. 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 DB

3. Payment Succeeded

Stripe processes payment
  → Webhook: invoice.payment_succeeded
  → API verifies signature
  → Convex: handlePaymentSucceeded
  → Status set to "active", period extended

4. 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:

  1. Signature Verification: Every webhook is verified using Stripe's signature
  2. Environment Validation: Checks all required environment variables at startup
  3. Error Handling: Proper error responses and logging
  4. Metadata Validation: Ensures userId exists in subscription metadata

Troubleshooting

Webhook Not Receiving Events

Check:

  1. Is your ngrok tunnel active? (ngrok http 3017)
  2. Is your Next.js dev server running? (pnpm dev)
  3. Is the webhook URL correct in Stripe dashboard?
  4. Are you in the correct Stripe mode (test/live)?

Signature Verification Failed

Check:

  1. Is STRIPE_WEBHOOK_SECRET set correctly in .env.local?
  2. Did you restart the dev server after adding the secret?
  3. 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:

  1. Are all price ID environment variables set?
  2. Do they match the IDs from STRIPE_PRODUCT_SETUP.md?
  3. 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_abc123
Received webhook: customer.subscription.updated
Subscription updated: sub_1234567890
Received webhook: invoice.payment_succeeded
Payment succeeded for subscription: sub_1234567890

Production Deployment

Vercel/Production Setup

  1. Add all environment variables to your hosting platform
  2. Update webhook URL in Stripe to production domain:
    • https://homepage.dev/api/stripe/webhook
  3. Use live mode keys and webhook secret
  4. 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:

  1. ✅ Webhook handler code created
  2. ⏳ Create checkout flow (app/api/stripe/checkout/route.ts)
  3. ⏳ Create customer portal (app/api/stripe/portal/route.ts)
  4. ⏳ Add frontend upgrade buttons
  5. ⏳ Test end-to-end subscription flow

Reference Documents:

  • docs/STRIPE_PRODUCT_SETUP.md - Product and price IDs
  • docs/PHASE_1_IMPLEMENTATION_COMPLETE.md - Backend tier enforcement
  • docs/TIER_MANAGEMENT_PLAN.md - Complete implementation roadmap

On this page