Start by checking whether bots are bypassing your forms
When a WooCommerce store is still getting spam user registrations after adding Cloudflare Turnstile or Google reCAPTCHA, I do not start by tweaking CAPTCHA settings. I first check whether bots can create accounts without touching the frontend registration form at all.
That is the key issue in a lot of these cases. CAPTCHA only helps when the request passes through the form where the CAPTCHA is loaded and validated. If a bot is posting directly to a WordPress, WooCommerce, REST API, XML-RPC, or checkout endpoint, your CAPTCHA never gets a chance to run.
The first things I check are whether these routes are open:
/wp-json//wp-json/wc//wp-json/wp/v2/users/xmlrpc.php/wp-login.php- WooCommerce checkout account creation
If those things are not blocked or restricted, then that is almost always the cause. If they are already blocked properly and spam registrations are still appearing, then I start treating it as a possible compromised site, plugin vulnerability, or malicious code issue.
Why CAPTCHA often fails on WooCommerce spam registrations
A common mistake is assuming that adding CAPTCHA to the WooCommerce registration page protects every possible way a user account can be created. It usually does not.
WooCommerce and WordPress have multiple account creation paths. You might have CAPTCHA on the normal my-account registration form, but that does not automatically mean it protects:
- Account creation during checkout
- Direct REST API requests
- XML-RPC requests
- Requests to
wp-login.php?action=register - Plugin-specific registration endpoints
This is why store owners can install Turnstile, reCAPTCHA, or another anti-spam plugin and still see new junk users appear. The CAPTCHA may be working perfectly on the form where it was added, but the bots are not using that form.
In my experience, guides that say “just install a CAPTCHA” gloss over this. CAPTCHA is useful for frontend form abuse. It is not a full account-creation firewall. If bots are calling endpoints directly, the normal fix is to completely block access to the endpoints they do not need.
Check your logs before changing random settings
The fastest way to confirm the source is to look at access logs, Cloudflare events, or security plugin logs around the time the spam accounts were created. You are looking for POST requests that happen right before the new user appears.
A typical pattern might look like repeated requests from the same IP range or hosting ASN to paths such as:
POST /wp-json/wp/v2/usersPOST /wp-json/wc/v3/customersPOST /xmlrpc.phpPOST /wp-login.phpPOST /checkout/or your store’s checkout URL
For example, if you see a new customer account created at 2:14 PM and the logs show a POST to /wp-json/wc/v3/customers from an unfamiliar server IP at the same time, that is strong evidence the bot did not use your visible registration page. If the request never loaded your registration page, then your Turnstile or reCAPTCHA widget was irrelevant for that request.
What usually stops this is not a better CAPTCHA. It is blocking the direct path the bot is using, especially when the store does not need that path publicly available.
Block the endpoints most WooCommerce stores do not need
For many WooCommerce stores, it is rare that public visitors need access to these account-related API endpoints. If you are not using the WordPress REST API for customer creation, a mobile app, a headless frontend, or an external integration that relies on these routes, it is generally safe to block them from public traffic.
The main endpoints I would look at first are:
/wp-json/wc/, especially WooCommerce customer routes/wp-json/wp/v2/users/xmlrpc.php
Cloudflare is a good place to do this because the request can be blocked before it reaches WordPress. That reduces server load and keeps the request away from plugins and PHP entirely.
Cloudflare WAF rule examples
In Cloudflare, we can create WAF custom rules that block or challenge suspicious POST requests. If the store does not need these endpoints, I prefer blocking instead of challenging. A challenge can still create noise, and legitimate shoppers should not normally be posting to these paths directly.
Useful patterns to block include:
- URI path contains
/xmlrpc.php - URI path contains
/wp-json/wp/v2/users - URI path contains
/wp-json/wc/and request method equalsPOST - URI path equals
/wp-login.phpand request method equalsPOST, if you do not need public login or registration there
You can make the rules broader or narrower depending on your setup. If your admin team logs in through wp-login.php, you may not want to block it globally. In that case, you can allow your country, office IPs, VPN IPs, or known admin users, then challenge or block the rest.
For API routes, I usually start with paths rather than countries or user agents. Countries, ASNs, and user agents can help when there is a clear pattern, but bots rotate those easily. If the endpoint is not needed, blocking the endpoint is cleaner.
Disable XML-RPC unless you know you need it
XML-RPC is old WordPress functionality, and most WooCommerce stores do not need it. It is highly unlikely that a modern shop depends on xmlrpc.php, unless there is a specific legacy service, remote publishing tool, mobile app, or plugin using it.
Before disabling it, I would ask a simple question: do we know of anything on the site that requires XML-RPC? If the answer is no, I block it.
You can disable XML-RPC in a few ways:
- Use a security plugin such as Wordfence or Solid Security
- Block
xmlrpc.phpwith a Cloudflare WAF rule - Block it at the web server level with Nginx or Apache rules
I like blocking it at Cloudflare or the server level because WordPress never has to process the request. If something breaks after blocking XML-RPC, that tells you a service was relying on it. In most client stores I have seen, nothing breaks.
Do not forget checkout registration
WooCommerce can create user accounts during checkout, and that flow is separate from the standard registration page. This is easy to miss. A store owner may add CAPTCHA to the WooCommerce account registration page, then leave checkout account creation unprotected.
Evidence that the spam is coming through checkout includes:
- Spam users are created at the same time as abandoned or failed checkout activity
- Logs show POST requests to the checkout URL instead of the account registration URL
- The spam accounts have billing fields, partial checkout data, or customer metadata
- Disabling account creation during checkout temporarily stops the spam
If checkout registration is the source, we need to secure that specific flow without making checkout harder for real customers. The best option depends on the store. Some shops can disable automatic account creation during checkout and let customers check out as guests. Others need accounts for subscriptions, memberships, wholesale pricing, or repeat ordering.
If accounts are required, then CAPTCHA or Turnstile needs to be applied to the checkout flow itself, not just the account registration page. I would also look at rate limiting POST requests to checkout, blocking obvious bot traffic at Cloudflare, and checking whether a plugin is exposing an easier registration endpoint in the background.
Watch for legitimate API edge cases before blocking everything
Blocking /wp-json/, wp-login.php, or xmlrpc.php can be very effective, but we still need to account for edge cases. They are not standard on most WooCommerce stores I see, but they do exist.
Be more careful if the store uses:
- A mobile app that talks to WooCommerce through the REST API
- A headless frontend
- External inventory, ERP, CRM, shipping, or fulfillment tools
- Custom checkout or customer portal integrations
- Payment or subscription systems that rely on API callbacks
The WordPress REST API is needed if you actually use the API. That said, many services now use native WordPress plugins instead of direct public API access, so a lot of stores can block sensitive routes without affecting normal operations.
The practical approach is to block the specific abused paths first, then test checkout, login, account creation, payment callbacks, shipping rates, and any connected services. If something legitimate needs access, allowlist that service by IP, authenticated method, or a narrower path instead of reopening the entire endpoint to the public.
