Stripe sends webhook events to this endpoint so your app can create/update subscriptions and log billing history even if the user closes the browser. The handler verifies Stripe’s signature, processes the event, and updates Supabase using a service client.
- Endpoint:
POST /api/webhooks/stripe - File Location:
src/app/api/webhooks/stripe/route.ts - Authentication: Stripe signature (
stripe-signatureheader) - Requires:
STRIPE_SECRET_KEY,STRIPE_WEBHOOK_SECRET, Supabase service client (SUPABASE_SECRET_KEY)
Headers & Payload
HMAC signature Stripe uses to prove the event originated from your account. Verified with
stripe.webhooks.constructEvent.Raw request body (no JSON parsing before verification). The handler reads the text stream directly, then constructs the event.
Events Handled
| Event | Action |
|---|---|
customer.subscription.created | Upserts a row in user_subscriptions with plan, billing cycle, trial dates, and metadata |
customer.subscription.updated | Updates plan changes, cancellation flags, billing periods, and feedback fields |
customer.subscription.deleted | Marks the subscription as cancelled and timestamps canceled_at |
invoice.payment_succeeded | Inserts a row in payment_history (amount, currency, status, invoice URL, payment intent) |
invoice.payment_failed | Logs failed payments so you can notify the user or retry |
user_id, user_email) links each event back to the correct Supabase user.
Database Side Effects
user_subscriptions: Stores Stripe customer/subscription IDs, plan, billing cycle, current/next period, cancellation info, trial info, and feedback.payment_history: Stores Stripe invoice + payment intent IDs, amount (in cents), currency, status, hosted invoice URL, and timestamps.
createServiceClient() (service key) to bypass RLS safely.
Implementation Snippet
src/app/api/webhooks/stripe/route.ts (trimmed)
subscription.metadata.user_id, and performs an upsert/update/insert with full timestamp handling.
Setup Steps
1
Expose endpoint
Deploy the Next.js API route (
/api/webhooks/stripe) to Vercel. Ensure the route is reachable over HTTPS (Stripe requires TLS).2
Set environment variables
.env
3
Configure Stripe Dashboard
- Stripe Dashboard → Developers → Webhooks
- Toggle Live mode (top-right)
- Click Add endpoint
- URL:
https://yourdomain.com/api/webhooks/stripe - Select the five events listed above
- Save and copy the signing secret (
whsec_...)
4
Add preview/test endpoints (optional)
Create a second endpoint pointing to your preview domain or use Stripe CLI for local testing (
stripe listen --forward-to localhost:3000/api/webhooks/stripe).Testing
- Local (Stripe CLI)
- Live mode
✔ Response: 200 and confirm Supabase tables updated.Troubleshooting
400 · Signature verification failed
400 · Signature verification failed
Cause: Payload mutated before verification, wrong signing secret, or missing
stripe-signature header.Fix: Ensure you read req.text() once, never call JSON.parse before verification, and double-check STRIPE_WEBHOOK_SECRET.No user ID in metadata
No user ID in metadata
Cause: Checkout session or portal event was created without
metadata.user_id.Fix: Ensure /api/checkout_sessions adds metadata and Customer Portal actions always originate from existing subscriptions (the handler fetches metadata from the subscription record).Payment records missing
Payment records missing
Cause:
invoice.payment_succeeded didn’t include a subscription ID (rare on manual invoices).Fix: The handler already fetches the subscription via stripe.subscriptions.retrieve. Ensure the invoice is linked to a subscription, or ignore the event if it’s unrelated.Webhook succeeds but database unchanged
Webhook succeeds but database unchanged
Cause: Supabase service client lacks permissions or table names differ.Fix: Confirm
SUPABASE_SECRET_KEY is set and RLS policies match the schema created in supabase/migrations/20240101000000_create_user_profiles.sql.Related Documentation
Create Checkout Session
Generates the Checkout session that seeds webhook metadata.
Customer Portal
Lets users manage subscriptions; portal actions also hit this webhook.
Payments with Stripe
End-to-end Stripe setup, product configuration, and webhook forwarding tips.
Environment Variables
Reference for Stripe and Supabase secrets required by this endpoint.