Your domain runs through Cloudflare (orange-cloud proxy). Stripe's event deliveries show a 403, or a 302, and your server logs show nothing. Cloudflare answered before your code ran.
A webhook is a server-to-server POST. Cloudflare's challenges check "whether a visitor to your site is a real human" by evaluating the browser, and a Challenge Page returns "a full HTML page for the user's browser to render and solve" (Cloudflare docs: Challenge Pages). Stripe has no browser. Stripe lists 401, 403 and 405 as what you see "when the destination URL has access restrictions", and says "we consider redirect responses to webhook requests as failures" (Stripe docs: Receive events). So a challenge page (a 4xx in Stripe's log) or a Cloudflare Access login redirect (a 302) is a failed delivery, every time.
Three Cloudflare features do this to webhooks: Bot Fight Mode (free plan), Super Bot Fight Mode or a WAF custom rule with a challenge or block action, and Cloudflare Access in front of the app.
cf-mitigated: challenge and has content-type text/html (Cloudflare docs: cf-mitigated). A 302 to your team's cloudflareaccess.com login page points to Cloudflare Access.curl -i -X POST -H 'content-type: application/json' -d '{}' https://yourdomain.com/api/webhooks/stripe
# Your own code: a 400 (unsigned request rejected).
# Cloudflare challenge: text/html with "cf-mitigated: challenge".
# Cloudflare Access: a 302 to a login page.
A request from your laptop is not scored the same way as Stripe's servers, so a clean curl result doesn't prove Stripe gets through. Security Events and Stripe's delivery log are the real evidence.
Cloudflare is explicit: "You cannot bypass or skip Bot Fight Mode using WAF custom rules or Page Rules", because it "operates in a separate evaluation pipeline where Skip, Bypass, and Allow actions have no effect" (source). Your options:
On Pro and above, "custom rules are executed before Super Bot Fight Mode", and a custom rule with the Skip action lets a request "bypass the Super Bot Fight Mode phase" (Cloudflare docs: Super Bot Fight Mode). The same Skip action can skip all remaining custom rules, rate limiting rules and managed rules (Cloudflare docs: Skip options). Scope it to the webhook path and Stripe's webhook IPs, and put it above the rule that challenges:
(http.request.uri.path eq "/api/webhooks/stripe"
and http.request.method eq "POST"
and ip.src in {3.18.12.63 3.130.192.231 13.235.14.237 13.235.122.149 18.211.135.69
35.154.171.200 52.15.183.38 54.88.130.119 54.88.130.237 54.187.174.169
54.187.205.235 54.187.216.72 35.157.207.129 3.69.109.8 3.120.168.93})
Action: Skip -> All remaining custom rules, All Super Bot Fight Mode rules
IP list copied from Stripe's page on Oct 20, 2026. Check the current list before you paste it, or keep it in a Cloudflare IP list.
Skipping a phase doesn't skip products that run outside the Ruleset Engine, such as Browser Integrity Check; those need the "Skip products" option, and Bot Fight Mode can't be skipped at all (source).
If the whole app sits behind Access, Stripe gets the login redirect. Cloudflare's documented pattern for an endpoint that must stay public is a separate Access application for just that path, with a Bypass policy; Bypass "disables any Access enforcement for traffic that meets the defined rule criteria" (Cloudflare docs: Access policies). Create an application for yourdomain.com/api/webhooks/stripe with a Bypass policy. Bypass can't use identity selectors like email, but it can use the IP ranges selector, so you can include only Stripe's webhook IPs instead of Everyone. Note that bypassed requests are not logged by Access (source).
Every exemption above lets more traffic reach your code without Cloudflare's checks, so the signature check has to work. Stripe recommends both: "IP allowlisting" and "Signature verification" (source). If you're not sure yours rejects forged events, see can someone fake a Stripe payment.
Paste your webhook URL. WebhookProof sends 3 harmless test requests and tells you whether Stripe can reach it and whether it rejects unsigned events. If it finds a problem, the $9 fix pack gives you the exact copy-paste fix for your stack (refundable for 14 days).
Run the free checkA challenge (from Bot Fight Mode, Super Bot Fight Mode or a WAF rule) needs a browser to solve it. Stripe sends a server-to-server POST, cannot solve it, and Stripe counts the 403 as a failed delivery.
Not with a rule. Cloudflare says Skip, Bypass and Allow actions in WAF custom rules or Page Rules have no effect on Bot Fight Mode. Turn it off, or add IP Access rules that allow Stripe's webhook IPs, since Bot Fight Mode does not trigger when an IP Access rule matches first.
Create an Access application for only the webhook path with a Bypass policy (optionally limited to Stripe's webhook IP ranges). Keep verifying the Stripe signature in your code.
On Stripe's Domains and IP addresses page (docs.stripe.com/ips), under Webhook notifications, also downloadable as text and JSON. Stripe announces changes seven days ahead on its API announce mailing list.
Stripe webhook not working? Check these 7 things in order · A customer paid but didn't get access. What went wrong? · No signatures found matching the expected signature for payload · Stripe webhook failing with a 307 or 308 redirect
Last reviewed 2026-10-20. WebhookProof is an independent tool, not affiliated with Stripe, Inc.. Quotes are from the linked documentation.