Fixing Bolt.new & v0 Next.js Stripe Webhook Desync, App Router Leaks & Hydration Crashes
Bolt.new and v0 Next.js applications regularly drop Stripe subscription upgrades because AI-generated webhook routes parse JSON bodies before verifying `stripe-signature` headers, omit event-ID idempotency checks, and rely on fragile client-side success-page redirects. A production-safe billing pipeline requires raw-body signature verification, atomic database transactions, and server-side session middleware.
Symptom → Root Technical Cause → Surgical Engineering Fix
| What You Experience | Why the AI Agent / App Is Stuck | Aquarel's Surgical Engineering Fix |
|---|---|---|
| Customers pay on Stripe Checkout, but their account remains on the Free tier in Postgres/Supabase | Fulfillment was wired to the browser `/success?session_id=...` redirect instead of an authenticated server webhook, or `req.json()` corrupted the raw payload needed for `stripe.webhooks.constructEvent`. | Read raw body via `await req.text()`, verify `stripe-signature`, and fulfill entitlements atomically on the server. |
| Duplicate Stripe webhook retries create duplicate user subscriptions or corrupt credit balances | Webhook handler lacks an idempotency table tracking processed `event.id` values and executes non-atomic read-then-write queries. | Record `stripe_event_id` with a unique constraint inside the same database transaction that updates subscription state. |
| Next.js production build fails or leaks database credentials into the browser bundle | AI assistant imported server database clients inside `'use client'` components or omitted `'server-only'` guards. | Enforce `'server-only'` package boundaries on all billing/DB modules and validate Server Actions with session guards. |
# Aquarel Vibe-Safe Handover: Next.js App Router & Stripe Billing Guardrails
## 1. Stripe Webhook Integrity (`src/app/api/webhooks/stripe/route.ts`)
- ALWAYS read the request body with `const rawBody = await request.text()` before calling `stripe.webhooks.constructEvent`.
- NEVER fulfill subscriptions or credits from client-side `/checkout/success` pages.
- Every webhook handler MUST be idempotent on `event.id`.
## 2. Server / Client Boundary Protection
- Every module under `src/server/**` MUST `import 'server-only'` at line 1.
- Every Server Action MUST verify the authenticated session before mutating database state. Why AI-Generated Stripe Webhooks Desynchronize in Production
AI coding tools love generating optimistic happy-path code: user clicks Checkout, lands on /success, and a client component updates the database. In real production environments, tabs close, webhooks arrive out of order, and checkout.session.completed races with customer.subscription.created. We replace fragile client-side billing hooks with an idempotent, signature-verified server webhook pipeline.
Broken AI Pattern vs. Surgical Engineering Fix
- Broken AI Pattern: Fulfilling subscriptions inside a Next.js
'use client'/checkout/successpage, or parsing the Stripe webhook request viaconst body = await req.json()before signature verification and running unguardedUPDATE users SET plan = 'pro'queries with zero event-ID deduplication. - Surgical Engineering Fix: Read the raw payload buffer via
const rawBody = await req.text(), verifystripe-signatureusingstripe.webhooks.constructEvent, recordevent.idin astripe_webhook_eventsidempotency table, and update subscription state inside an atomic server transaction guarded byimport 'server-only'.