Checkout Bouncer

Notes

The Store API checkout route, and why a bot does not need your checkout page

Bots can place WooCommerce orders through the Store API checkout route without ever loading your checkout page. Here is how, and what protects it.

4 min read

You added a captcha to the checkout page and the attack kept going. The orders still arrive, and your page analytics show almost nobody on checkout. That is not a contradiction. The bot is not using your checkout page.

A checkout page is just a form

A checkout page collects details in the browser and sends them to the server. The server is where the order is made. Whatever the page does in the browser, a script can skip it and talk to the server directly.

On a WooCommerce store, there are two main places the server listens for a checkout. The classic checkout posts to an AJAX endpoint. The Checkout block posts to the Store API. This note is about the second one, because many store owners do not know it exists on their site.

What the Store API is

The Store API is a public REST API that ships inside WooCommerce. It powers the Cart and Checkout blocks. It lives under the /wp-json/wc/store/v1/ path and includes routes for the cart, adding items, and placing an order.

The checkout route is /wp-json/wc/store/v1/checkout. A POST request to that route, with a cart, billing details, and a payment method, creates an order and asks the gateway to charge the card.

This route is there on most current WooCommerce installs, whether or not your checkout page uses the block. A store that still runs the classic shortcode checkout usually has the Store API answering in the background too.

How a bot uses it

A script does not need a browser for this. It needs to make a few HTTP requests in the right order:

  1. Request the cart route to get a session and the nonce or cart token the Store API sends back in the response headers.
  2. Add a cheap product to the cart through the add item route.
  3. Post billing details and payment data to the checkout route.
  4. Read the response to learn whether the card was accepted, then repeat with the next card.

None of these steps loads your checkout page. So a check that lives only on the page, such as a captcha widget, a honeypot field in the form, or a script that watches mouse movement, never runs.

The nonce does not stop this either. A nonce proves that the request came with a valid session, and the Store API hands one out to anyone who asks for a cart. It protects against cross-site requests, not against a script acting on its own behalf.

You can see it in your own logs

If you have access to server access logs, search for POST requests to the checkout route during the attack window.

grep "POST /wp-json/wc/store/v1/checkout" access.log | awk '{print $1}' | sort | uniq -c | sort -rn | head

This counts requests per IP address. A short burst of checkout posts with no matching page views for /checkout/ is a strong sign that a script is going straight to the API.

Keep in mind that behind a CDN or proxy, the first column may show the proxy address instead of the visitor. Check how your host writes the log before you trust the counts.

What actually protects this route

A protection has to run on the server, inside the Store API request, to cover this route. In practice that means one of these:

  • A check that requires a token from the browser and rejects the order on the server when the token is missing or bad. The Store API lets extensions send their own data with the checkout request, so a bot check can travel inside the same request.
  • A rate limit on the Store API. WooCommerce includes one, but it is off by default and you turn it on with a filter.
  • Gateway fraud rules, which judge the payment itself after the order is made.

The key point is that the server must refuse the order when the check fails. A check that only hides the Place order button in the browser does nothing for a script.

Should you turn the Store API off?

Some guides suggest blocking the Store API entirely. That breaks the Cart and Checkout blocks, and some plugins and themes depend on it for mini carts and other features. If your store uses the classic checkout and nothing else needs the API, blocking the checkout route can work, but test the whole buying flow before and after.

A safer default is to keep the route open and make it check every request the same way your visible checkout does.

Where Checkout Bouncer fits

Checkout Bouncer sends a reCAPTCHA v3 token with the Checkout block through Store API extension data, and verifies it on the server before the order is created. A request that comes straight to the route without a valid token gets refused, whether or not your checkout page was ever loaded.

See which doors into your checkout are standing open