HeHeedza

Why is my Stripe webhook failing with a 307 or 308 redirect?

Last reviewed

Short answer

Stripe treats any redirect (3xx) from a webhook endpoint as a failed delivery. Something is redirecting the request: apex to www, a trailing slash, http to https, or auth or locale middleware. Set the endpoint URL to the exact final URL and make sure no middleware runs on that route.

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.com to www.yourdomain.com (or the reverse) with a 307 or 308.
  • Trailing slashes. With trailingSlash: true in Next.js, /api/stripe/webhook 308-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.js NextResponse.redirect defaults 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/webhook

A 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.

Fix prompt
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.

Questions

Can I make Stripe follow the redirect?

No. Stripe treats 3xx responses as failures. The endpoint URL has to be the final URL.

What status should my endpoint return to an unsigned test POST?

A 400 from your signature check is a good sign. It means the request reached your code without a redirect.

Will Stripe resend the events that failed?

Stripe retries automatically for up to three days. You can also resend events from the endpoint's Event deliveries tab.

My endpoint returns 401 instead of a redirect. Is that related?

It's a close cousin. Something in front of your code is asking for authentication, usually deployment or password protection on the host, or a Supabase Edge Function deployed with JWT verification on. Point Stripe at an unprotected production URL, and turn off JWT verification for a webhook function. Your signature check is what keeps it safe.

Sources