From 37d5b04a8ece5d54606085f47dd7c9681d0d112d Mon Sep 17 00:00:00 2001 From: Moheeb Zara Date: Sun, 17 May 2026 03:55:30 -0700 Subject: [PATCH] fix(stripe): whitelist webhook path in proxy so Stripe is not redirected to /login proxy.ts auth-gates every non-public path and 307-redirects to /login. The Stripe webhook is called without a session, so it was being redirected; Stripe does not follow redirects, so every webhook delivery would have failed. Add /api/stripe/webhook to PUBLIC_ROUTES (scoped to the webhook path only) and correct the route/header comments that wrongly claimed there was no auth middleware. Caught by smoke-testing the deployed route (POST returned 307 instead of 400). --- app/api/stripe/webhook/[space]/route.ts | 7 +++++-- proxy.ts | 5 +++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/app/api/stripe/webhook/[space]/route.ts b/app/api/stripe/webhook/[space]/route.ts index f217573..b46152d 100644 --- a/app/api/stripe/webhook/[space]/route.ts +++ b/app/api/stripe/webhook/[space]/route.ts @@ -2,8 +2,11 @@ // path to know which signing secret to verify with). Raw body, signature // verify, idempotency on Stripe's stable event id, then map the subscription // lifecycle onto member_billing + member status (grace -> late, never auto- -// inactive). Unauthenticated by design (Stripe calls it); there is no auth -// middleware in this app, and every DB write is post-signature-verify. +// inactive). Unauthenticated by design (Stripe calls it). proxy.ts auth- +// gates everything by default and redirects to /login, which Stripe would +// NOT follow — so this exact path is whitelisted in proxy.ts PUBLIC_ROUTES. +// Trust is the per-space webhook signature; every DB write is +// post-signature-verify. import { NextRequest, NextResponse } from 'next/server' import type Stripe from 'stripe' import { createAdminClient } from '@/lib/supabase/admin' diff --git a/proxy.ts b/proxy.ts index f3d39b2..97c3c19 100644 --- a/proxy.ts +++ b/proxy.ts @@ -16,6 +16,11 @@ const PUBLIC_ROUTES = [ '/track', '/auth', '/api/health', + // Stripe calls this unauthenticated; it is verified by the per-space + // webhook signature, not a session. Must NOT be redirected to /login + // (Stripe does not follow redirects). Scoped to the webhook path only — + // the Stripe server actions are invoked from authenticated pages. + '/api/stripe/webhook', // Public form / waiver fill page. Only /f/[slug] lives here and must be // reachable anonymously (the page serves only published public forms and // submitForm enforces visibility). The /forms* management routes do NOT