How the pieces connect
Three boundaries matter in an App Router project. The browser only ever touches Stripe-hosted surfaces: it receives a Checkout Session url and is redirected to checkout.stripe.com, or it loads the Billing Portal via a returned URL. Your Route Handlers (app/api/.../route.ts) run on the server, hold the secret key, and call stripe.checkout.sessions.create and stripe.billingPortal.sessions.create. Stripe itself calls back into your app at app/api/webhooks/stripe/route.ts after the payment completes.
The redirect flow and the webhook are independent. The success_url returns the customer to your site, but it is not proof of payment — a user can close the tab. Treat the checkout.session.completed webhook as the only authoritative signal for fulfillment, and write to your database from there, not from the success page.