Notes
Block checkout submits somewhere else: the Store API route
The Checkout block posts JSON to a REST route. Code hooked to the classic checkout is never asked, which is why protection can look healthy and do nothing.
6 min read
You swap the checkout shortcode for the Checkout block. The page looks better, the fields behave, test orders arrive. Then something you rely on quietly stops working, and nothing in the admin says so. The WooCommerce Store API checkout route is why.
The reason is structural rather than mysterious. The block checkout does not post to the page it sits on. It talks to a REST route, and code that was waiting at the old door never hears the knock.
Where the WooCommerce Store API checkout route creates the order
WooCommerce exposes a public API for storefronts, the Store API, and the Checkout block is its main customer. The block sends JSON to the WooCommerce Store API checkout route, which lives under wc/store/v1. That request is what turns a cart into an order. No form post, no page reload, no wc-ajax endpoint.
An order also exists earlier than most people expect. When a shopper starts interacting with the block checkout, WooCommerce opens a draft order for the session. It carries the status checkout-draft and the store-api stamp. WooCommerce's own documentation puts it plainly: "Draft orders are created when customers start the checkout process while the block-based checkout is in place." Those rows are normal. They are not payments, and they are not abandoned carts in the marketing sense either.
The stamp is useful. Classic checkout marks its orders as created via checkout, and the Store API marks its own as store-api. A store that runs both can tell at the record level which door each order came through, without inferring it from timestamps.
Why code written for the classic checkout goes quiet
The classic flow runs a well known sequence: woocommerce_checkout_process while it validates the submission, then woocommerce_after_checkout_validation, then woocommerce_checkout_order_processed once an order exists. A decade of WooCommerce extensions hang off those names.
The Store API fires a different set entirely, including woocommerce_store_api_checkout_update_order_from_request, woocommerce_store_api_checkout_update_order_meta and woocommerce_store_api_checkout_order_processed. The source explains why the names were deliberately not reused. They exist so that an extension knows the order came from the block checkout and the Store API rather than the classic form.
So a validation check bound to the classic hooks is not broken and not disabled. It is simply never asked. That distinction matters when you are debugging, because everything about the plugin will look healthy: settings saved, keys valid, no errors anywhere. The only symptom is that orders arrive as though the check were not installed, which is exactly what happened.
A public route, by design
WooCommerce intends the Store API to be reachable without a login. The source says so directly: the Store API does not require authentication. A browser session carries a nonce header, and guests can carry a cart token so a cart survives without an account. Neither of those is authentication in the sense of proving who somebody is.
That is the correct design for a storefront API, and it is also the practical point of this note. If the route is public, then protection has to live on the route, not on the page that usually calls it. Anything that decides whether to run by asking which page the shopper is looking at has already lost. A REST request is not looking at a page at all.
What core gives you, and what it does not
WooCommerce does ship a rate limiter for the Store API, and its documentation is candid about the default: "Rate Limiting is available for Store API endpoints. This is optional and disabled by default."
Turning it on is a filter, woocommerce_store_api_rate_limit_options, which carries four settings. The enabled flag is false by default. The limit defaults to 25 requests and seconds to 10. And proxy_support is false, so the limiter counts by the connecting address only, unless you tell it your store sits behind a proxy or CDN. Rate limiting applies to POST requests, and only to visitors who cannot edit posts, so your own admin testing does not consume anyone's allowance.
Three details are worth carrying into your logs. When a request trips the limit, WooCommerce sends RateLimit-Limit, RateLimit-Remaining, RateLimit-Retry-After and RateLimit-Reset headers. Those are the cleanest evidence you will get that the limiter is doing anything. It also fires an action when a request trips the limit, so you can record the event yourself. And the refusal comes back with an HTTP status of 400, not the 429 most people grep for. That is a good way to stare straight past your own rate limiting in an access log.
Current WooCommerce source goes one step further with a feature named rate_limit_checkout. When enabled, it opts the checkout route in on stricter terms: three requests per sixty seconds. Whether that feature is available on your version is worth checking rather than assuming, and either way a rate limit is a brake, not a gate. It slows a run of attempts down. It does not tell a shopper apart from a script.
What to check on your own store
- Establish which routes your store actually uses. A store running the block checkout has the WooCommerce Store API checkout route live whether or not anyone told you. The Checkout Scanner reports the ones it finds.
- Place a test order through the block checkout and confirm that whatever protects your classic checkout produced a log line for it. A captcha that never runs produces none. Silence is the answer, not the absence of one.
- Group your orders by the route recorded on them. If protection covers one route and orders arrive through the other, that gap is your whole problem.
- Decide whether the core rate limiter should be on. If you enable it, set proxy support to match your hosting, or every shopper behind your CDN shares a single allowance.
- Watch draft orders. A steady trickle is ordinary; a flood of them from one source is a signal, and they are cheap to count.
Where Checkout Bouncer fits
Checkout Bouncer is a free, GPL plugin on WordPress.org that treats these routes as the actual subject rather than an afterthought. It is a free, GPL plugin on WordPress.org. It scores submissions with Google reCAPTCHA v3, the only engine it uses, covering the classic checkout and the Checkout block that posts to the WooCommerce Store API checkout route. Where a route structurally cannot carry a token, it offers to close that route instead of pretending to score it. It is checkout only, not login or registration or contact forms, and it is neither a firewall nor a malware scanner. A Pro tier is planned and does not exist, so there is nothing to buy. If you would rather do this by hand, the five checks above are the same ones the plugin automates.