Why this happens
Stripe's docs are explicit: "We consider redirect responses to webhook requests as failures", and the fix is to set the endpoint to the URL the redirect resolves to. Common sources of redirects in modern hosting:
- Domain redirects. Vercel and other hosts often redirect
yourdomain.comtowww.yourdomain.com(or the reverse) with a 307 or 308. - Trailing slashes. With
trailingSlash: truein Next.js,/api/stripe/webhook308-redirects to/api/stripe/webhook/. - http → https, if the endpoint was saved with
http://. - Middleware. Auth middleware that sends signed-out requests to
/login(Next.jsNextResponse.redirectdefaults to 307), or i18n middleware that adds/en.
A related failure: if the host puts password or deployment protection on the URL, you'll get a 401 instead.
How to fix it, step by step
1. See where the request ends up. Send an unsigned POST to the exact URL saved in Stripe:
curl -sS -o /dev/null -w "%{http_code} -> %{redirect_url}\n" \
-X POST https://yourdomain.com/api/stripe/webhookA healthy endpoint answers 400 (missing or invalid signature) with no redirect. If you see 307 -> https://www.yourdomain.com/... or 308 -> .../webhook/, that redirect target is the URL Stripe should use.
2. Update the endpoint in Stripe. In Workbench → Webhooks, open the endpoint and edit its URL to the final URL: right host, https, exact path and slash. Repeat for the live and test endpoints separately.
3. Keep middleware off the webhook route. In Next.js this file is middleware.ts, or proxy.ts from Next.js 16, where the convention was renamed. The matcher works the same in both:
// middleware.ts (or proxy.ts on Next.js 16+)
export const config = {
matcher: ['/((?!api/stripe/webhook|_next/static|_next/image|favicon.ico).*)'],
};Or return early at the top of the middleware/proxy function:
if (request.nextUrl.pathname.startsWith('/api/stripe/webhook')) return NextResponse.next();4. Check deployment protection. If preview or deployment protection is on, Stripe can't reach protected URLs. Point Stripe at your production domain, or use your host's documented bypass for automation.
5. Replay the failed events. Stripe retries undelivered events for up to three days. To recover sooner, open Event deliveries and resend the failed ones, and make sure your handler ignores events it has already processed.
Fix prompt for your AI builder
Paste this into Lovable, Bolt, Cursor, Claude, ChatGPT, Replit, v0 or whichever tool you build with. Replace the bracketed parts first.
Stripe webhook deliveries to https://yourdomain.com/api/stripe/webhook fail with a 307/308 redirect. Please:
1. Make sure middleware or proxy (middleware.ts / proxy.ts: auth, i18n, redirects) does not run on /api/stripe/webhook. Exclude it in the middleware matcher or return NextResponse.next() early for that path.
2. Check next.config for trailingSlash and redirects that would affect that path.
3. Tell me the exact canonical URL (host, https, path, trailing slash) I should save as the endpoint in the Stripe Dashboard.
Don't change the webhook handler logic or any other routes.How to catch this next time
This breaks after domain changes: adding www, moving hosts, or turning on a "redirect to primary domain" setting. Every one of those quietly breaks Stripe until someone notices unpaid access or missing orders.
Heedza checks your live checkout page on a schedule, stopping before payment, and emails you a screenshot and a fix prompt if the payment form or pay button disappears. It doesn't watch Stripe's webhook deliveries, so after a domain change re-run the curl check above. You can see what Heedza checks on your site with the free scan.