CheckoutAlarmWatch my app free

Stripe webhook 401 or 403 on Vercel: Deployment Protection

Event deliveries in Stripe shows 401 and your handler logs are empty. Your code never ran. On Vercel, a common reason is that Deployment Protection answered first and asked Stripe to log in. Stripe's docs list 401, 403 and 405 as errors that can mean “the destination URL has access restrictions”, and say to make sure the endpoint is publicly accessible and accepts POST. Stripe: fix HTTP status codes

Free tool: decode any Event deliveries status

What it would catch here

With a read-only Stripe key, CheckoutAlarm's Stripe check emails you when events older than 15 minutes still haven't been delivered to your endpoint. It runs at its next Stripe check: once a day on Free, hourly on Pro.

What it won't catch

It can't tell you the status code or that Vercel is the cause, it doesn't read your Vercel settings, and it doesn't alert when Stripe has no endpoint for an event. Its page check can pass while this is broken.

Plans

Free: 1 site, checked once a day. Pro: $19 a month, up to 3 sites checked hourly. You prove you own the site first. For Stripe we only take a restricted key with read access to Events, Webhook Endpoints and Charges. Full secret keys are refused. Compare plans

POST to the exact URL Stripe has

Send a JSON POST, because that is what Stripe sends, and look at the status:

curl -i -X POST \
  -H "Content-Type: application/json" \
  -d '{}' \
  https://your-url.example/api/stripe/webhook

Your handler should answer 400 here, because the request has no Stripe signature. We ran the same check on one of our own protected Preview URLs: the JSON POST got 401 with a body that said “Protected by Vercel Authentication”, and a POST without a JSON content type got a 302 redirect to vercel.com/sso-api. Stripe counts a redirect as a failure too. Stripe: fix HTTP status codes

Check which URL Stripe has

In Stripe, open the endpoint and read its URL. A *.vercel.app address, or a preview URL, is the first thing to suspect. Compare it with the one your production domain uses.

What each scope protects

Deployment Protection has two scopes. Standard Protection “protects all deployments except production domains”. All Deployments “protects all URLs, including production domains”. Vercel: Deployment Protection

The generated deployment URL counts as protected

Vercel's migration note says that once Standard Protection is on, the production generated deployment URL becomes restricted. A webhook pointed at that address, rather than your own domain, will be stopped. Vercel: Standard Protection

Vercel Authentication is a login wall

Vercel says people who reach a protected deployment “will encounter a Vercel login redirect”. Stripe can't sign in to Vercel. Vercel: Vercel Authentication Vercel also says Deployment Protection “requires authentication for all requests, including those to Routing Middleware”, so a middleware rule in your app can't let the request through. Vercel: Deployment Protection

Register your production domain

Set the endpoint URL in Stripe to your own domain, such as https://yourdomain.com/api/stripe/webhook. Under Standard Protection that domain isn't protected. Repeat the curl above and look for your handler's 400. Stripe needs a publicly accessible HTTPS URL. Stripe: set up your endpoint

If your production domain is protected

If you chose All Deployments, your own domain is behind the login too. Move the project to Standard Protection if you don't need production locked. Vercel: choose which URLs to protect

Testing against a protected preview

For a test endpoint, Vercel's Protection Bypass for Automation lets a request through when it carries a secret. For tools that can't set headers, it documents a query parameter named x-vercel-protection-bypass on the URL, and it shows a Stripe webhook URL as an example. Vercel: Protection Bypass for Automation

Treat that URL like a password. Each secret works on all deployments in the project until it is revoked, and regenerating it invalidates previous deployments until you redeploy. Vercel: configuring the bypass

Make one preview domain public

Deployment Protection Exceptions let you turn protection off for a list of preview domains. Vercel says a domain you add “becomes publicly accessible”, so only add one you are fine exposing. Vercel: Deployment Protection Exceptions

Let Stripe retry, or resend

In live mode Stripe tries to deliver an event for up to three days with exponential backoff. A sandbox retries three times over a few hours. Stripe: automatic retries For events that already failed, click Resend on the event in the Dashboard (up to 15 days after creation), or run stripe events resend <event_id> --webhook-endpoint=<endpoint_id> (up to 30 days). Stripe: manual retries

Check the deliveries and your data

Open the endpoint's Event deliveries and confirm new events show 200. Then check the records those events should have written. See recover missed Stripe webhook events, and 307 or 308 on Vercel if the status is a redirect. If the 401 comes from a Supabase Edge Function or your own auth middleware, read the Edge Function 401 guide.

Why does Stripe get 401 from my Vercel app?
If Deployment Protection covers the URL you gave Stripe, Vercel asks for a login before your code runs. Stripe can't log in, so the delivery fails. Stripe lists 401 and 403 as access restrictions and says to make sure the endpoint is publicly accessible and accepts POST.
Is my production domain protected?
Not under Standard Protection, which protects all deployments except production domains. It is protected if you picked All Deployments. The generated *.vercel.app deployment URL is restricted under Standard Protection, so don't register that one in Stripe.
Can I put the bypass secret in the Stripe webhook URL?
Vercel's docs show exactly that for Stripe, as the x-vercel-protection-bypass query parameter. The secret then sits in the URL, so anyone who can see the endpoint can use it to reach every deployment in the project until you revoke it. For a live endpoint, pointing Stripe at an unprotected production domain is the safer fix.
Will Stripe retry the failed events?
Yes. In live mode Stripe retries for up to three days with exponential backoff. In a sandbox it retries three times over a few hours. You can also resend an event by hand from the Dashboard (up to 15 days after it was created) or with the Stripe CLI (up to 30 days).
Does CheckoutAlarm catch this?
It catches the effect, not the cause. With a read-only Stripe key, its Stripe check emails you when events older than 15 minutes still haven't been delivered, at its next check (once a day on Free, hourly on Pro). It doesn't see the 401 or your Vercel settings, and it doesn't alert when Stripe has no endpoint for the event.

CheckoutAlarm doesn't see the 401 or your Vercel settings. With a read-only Stripe key, its Stripe check emails you when events older than 15 minutes still haven't been delivered, at its next check (once a day on Free, hourly on Pro).

CheckoutAlarm is new. It only checks sites you prove you own. How to verify

Watch my app free

Docs checked on Oct 22, 2026.