Large spike in 403 error responses from the ajax cart form cloudflare verification

Our app is seeing a large spike in 403 http responses from Shopify’s cart api and section api. The response html seems to be a validation page. It seems odd that these endpoints are validated like this and require a user to do an action.

Shopify Ajax API requests should complete successfully without a verification. Instead the page the customer is viewing should be validated if required.

The chart below shows the scale and impact over the last couple of weeks. This isnt the first time this has happened. Its effecting all stores using our app and not specific stores

Hey @James_Wilson - thanks for flagging this. This could potentially be related to bot protection on our end, although we have seen similar false positives with legitimate storefront traffic (429 errors in this case): Aggressive bot detection with 429 errors AJAX Cart API - #103 by Paige-Shopify

Could you share a request ID from one of the 403 responses? If one isn’t available, an approximate timestamp with the timezone and the exact Ajax API endpoint that returned the response for a timeframe where you saw a large surge would be super helpful so I can correlate in our logs here. Hope to hear from you soon!

Hey @Alan_G Thanks for taking a look at this. There is no request Id in the response headers when the request returns a 403 or 429.

Im not seeing any large surges, just a constant flow since August 26th. Similar patten with 429 requests. If you turn on a VPN and go to any store you can recreate this pretty quickly.

This is an example request I did where I access a store via a VPN. Site loaded with no issues, added to cart with success, then increased the quantity in the cart and I got a 429 response.

these are the response headers for that failed request:

accept-ch: Sec-CH-UA-Bitness, Sec-CH-UA-Arch, Sec-CH-UA-Full-Version, Sec-CH-UA-Mobile, Sec-CH-UA-Model, Sec-CH-UA-Platform-Version, Sec-CH-UA-Full-Version-List, Sec-CH-UA-Platform, Sec-CH-UA, UA-Bitness, UA-Arch, UA-Full-Version, UA-Mobile, UA-Model, UA-Platform-Version, UA-Platform, UA

alt-svc: h3=“:443”; ma=86400

cf-cache-status: DYNAMIC

cf-mitigated: challenge

cf-ray: a3485e149812f186-MIA

content-type: text/html; charset=UTF-8

critical-ch: Sec-CH-UA-Bitness, Sec-CH-UA-Arch, Sec-CH-UA-Full-Version, Sec-CH-UA-Mobile, Sec-CH-UA-Model, Sec-CH-UA-Platform-Version, Sec-CH-UA-Full-Version-List, Sec-CH-UA-Platform, Sec-CH-UA, UA-Bitness, UA-Arch, UA-Full-Version, UA-Mobile, UA-Model, UA-Platform-Version, UA-Platform, UA

cross-origin-embedder-policy: require-corp

cross-origin-opener-policy: same-origin

cross-origin-resource-policy: same-origin

date: Wed, 02 Sep 2026 00:23:41 GMT

nel: {“report_to”:“cf-nel”,“success_fraction”:0.01,“max_age”:604800}

origin-agent-cluster: ?1

permissions-policy: accelerometer=(),camera=(),clipboard-read=(),clipboard-write=(),geolocation=(),gyroscope=(),hid=(),magnetometer=(),microphone=(),payment=(),publickey-credentials-get=(),screen-wake-lock=(),serial=(),sync-xhr=(),usb=(),xr-spatial-tracking=*

referrer-policy: same-origin

report-to: {“group”:“cf-nel”,“max_age”:604800,“endpoints”:[{“url”:“https://a.nel.cloudflare.com/report/v4?s=WP0ykhh5h5CSRhdOPeQe4omgFSkLJAhxHAGVxkBEVZRI3DZ2BAwxeUKUeGWb7sd2kGtRw8dt6b41tn%2FZ%2FL%2FA7Slpkt3KFPPmP6f8CeC3rpKkN5cPtJ2c0GXXjXmEtDWmlA%3D%3D”}]}

server: cloudflare

server-timing: chlray;desc=“a3485e149812f186”

x-content-type-options: nosniff

x-download-options: noopen

x-frame-options: SAMEORIGIN

x-permitted-cross-domain-policies: none

x-xss-protection: 1; mode=block

It seems the new settings that have been implemented are just too strict. The user doesn’t even know they need to verify unless the page is refreshed. It seems to harshly effect VPN users which is a large proportion of internet users nowadays.

Do you want me to send you a file export of a number of example failed requests headers?

Hey @James_Wilson - thanks for the detailed headers, that was exactly what I needed. I was able to trace that cf-ray on our end.

Just confirming what you suspected: that response is coming from rate limiting at our edge (the same protection layer from the thread I linked), not from Shopify’s origin - which is also why there’s no x-request-id, the request never gets that far. Nothing about your session looked bot-like on our end, so this is a false positive rather than your app being flagged. And once a client trips it, subsequent cart requests from that client get challenged for a window, which lines up with the pattern you’re seeing.

A few things that would help me build the case internally:

  1. Yes please on the export - cf-ray, UTC timestamp, store domain, endpoint and status code per request is enough. A spread across different stores and regions is more useful than a lot from one store.
  2. Do the 403s also carry cf-mitigated: challenge, or only the 429s? A cf-ray from a 403 specifically would be great - they may be coming from a different layer.
  3. Roughly how many cart requests does your app make per shopper interaction (e.g. polling /cart.js, or a /cart/change on each quantity update)? Not pointing fingers - I just want to understand how a typical shopper session adds up.

On the VPN side, I definitely get the frustration. Paige mentioned in the other thread that false positives for VPN users aren’t fully solved yet, and I’ll make sure your data gets to the team working on it.

One thing worth considering in the meantime: since the challenge comes back on an XHR, the shopper never sees it. Checking for cf-mitigated: challenge on Ajax cart responses and handling it (e.g. prompting a one-time page refresh so the verification can actually render) at least makes it not a silent failure. Not super ideal, but it’s what I’d do while the platform side is being worked on.

Let me know if I can help out further or if you’d like to send that info via DM and I can set one up on my end here.

Thanks for confirming what is happening. Answered your questions below:

  1. Here is a link to the export csv for the last 6 hours of logs for a 403. Annoyingly its the opposite of what you want. Its mainly one store in one region.
  2. Yes 403 has the cf-mitigated: challenge.
  3. Our app doesn’t poll the cart. Except after a cart mutation that was called from outside of our app. But our app controls the cart mutations - so its rarely required. I have noticed on the website for the store in the export there are other apps that are polling the cart multiple times on page load (counted 8 times) and after cart mutations.

I have considered the page refresh on the response just doesn’t feel right to do it on the ajax call instead of the page load. But if thats your recommendation ill do some tests on a sample of stores

Interested to see if any in the export are false positives

Thanks

I added the page refresh for the test store I mentioned as you suggested. The Cloudflare verification page now shows when the ajax request fails prompting the user to verify. Which I agree is a better solution than the silent fail.

The problem is when browsing and adding to cart like a regular customer, I’m having to verify every couple of minutes. Which does indicate there system is too strict. A single verification doesn’t persist for the session.

Hey @James_Wilson - thanks for the export and for testing the refresh, that gave me a lot to work with.

Two different things are going on here, so just want to separate them:

The 403 export. Just confirming what you suspected might be the case - these aren’t false positives. 3,952 of the 3,954 rows are one store, one region, and the timing is a dead giveaway: 93% of the hits land on minutes divisible by 3 (:00, :03, :06…) with zero on the minutes in between, in bursts of up to 179 requests in a few seconds. From what I could tell in our logs, that appears to be automated traffic running in a headless browser that loads the storefront, executes the theme and app scripts (yours included), and fires cart calls - so the challenges land in your telemetry looking like customer failures. Our edge is doing the right thing there. I’d suggest excluding sessions with no human interaction signals (pointer/scroll/focus events before the first cart call) from your failure counts, which should collapse most of that spike. If you’re seeing the same pattern on other stores, I’m happy to look at those too.

Your VPN repro. That’s another issue from what I can tell, and it matches the not-fully-resolved bucket Paige called out in the other thread. On the re-verify-every-couple-minutes piece: solving the challenge clears the block for that moment, but it doesn’t change how the session was classified in the first place, so a few more cart writes can trip it again. I definitely get that’s not super ideal. I’m going to raise this internally.

Two things that would help us out on our end:

  1. A cf-ray + UTC timestamp for the two non-freddystore rows in your export (rovebyronbay and humidity-lifestyle) - those look like the genuine false-positive shape.
  2. A cf-ray from a VPN repro on any store other than the one in the export.

Let me know if I can clarify anything on our end here.