A real-world WooCommerce incident involving rotating IPs, browser-like bot traffic, WordPress lifecycle ordering, signed cart proofs, Redis, and keeping UCP/MCP agent commerce online.
A WooCommerce store we operate started showing a strange traffic pattern.
At first glance, it looked like growth.
Traffic increased sharply. The basket page was receiving unusual attention. Google Analytics showed thousands of new sessions.
Then we looked closer.
Engagement was almost zero.
Revenue from the traffic was zero.
And thousands of requests were hitting WooCommerce URLs containing:
?add-to-cart=...
This turned into an interesting engineering problem because the obvious solutions were also the wrong solutions.
We could not simply block the IP addresses.
We could not block the browser User-Agent.
We did not want to block legitimate crawlers indiscriminately.
And most importantly, we are actively building infrastructure that allows legitimate AI shopping agents to interact with WooCommerce.
We needed to stop abusive automation without breaking agentic commerce.
This is how we approached it.
The traffic initially looked impressive.
Thousands of sessions appeared, with large increases in active and new users.
But almost none of those sessions behaved like customers.
We saw extremely low engagement and a disproportionate amount of traffic around the WooCommerce basket.
Server logs provided the real picture.
Requests repeatedly looked like this:
GET /shop/example-product/?add-to-cart=1234
GET /basket/
Sometimes the second request arrived only one or two seconds after the first.
The User-Agent often looked completely ordinary:
Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)
AppleWebKit/537.36 (KHTML, like Gecko)
Chrome/150.0.0.0 Safari/537.36
Blocking that User-Agent would have been a bad security decision.
Chrome 150 is not an identity.
It is just a string.
Across the available logs, we extracted unique IP addresses that used that Chrome/150 signature while requesting add-to-cart.
The result:
8,096 unique IP addresses
This immediately changed the problem.
Traditional per-IP rate limiting or Fail2ban-style blocking could still remove individual offenders, but it was no longer an effective primary defense.
Most addresses appeared only once or twice.
The network was rotating faster than an IP reputation system could meaningfully respond.
ASN sampling also showed significant hosting and proxy infrastructure, including traffic associated with several hosting networks.
But ASN blocking was not the answer either.
The fundamental problem was not:
Which IP is malicious?
The better question was:
Should this request be allowed to perform an expensive state-changing WooCommerce operation?
That distinction became the foundation of the solution.
Not all of the traffic was pretending to be a browser.
We also observed recognizable crawlers hitting cart operations, including identifiers such as:
SERankingBacklinksBot
MJ12bot
Reflectionbot
Those could be handled through explicit bot detection.
But during testing we discovered another important issue.
Correctly detecting a bot does not help if enforcement happens too late in the WordPress lifecycle.
WooCommerce processes legacy add-to-cart requests during wp_loaded.
Our enforcement path was initially occurring later.
That meant a request could be correctly identified as a bot while WooCommerce had already been given an opportunity to process the cart operation.
The fix was conceptually simple:
add_action(
'wp_loaded',
array( __CLASS__, 'observe_request' ),
1
);
Priority matters.
WooCommerce's cart processing happens later.
Security enforcement must happen before the mutation, not after it.
After correcting the lifecycle ordering, controlled tests produced the expected result:
MJ12bot -> 403
Reflectionbot -> 403
SERanking -> 403
Normal browser -> normal WooCommerce flow
That solved recognizable bots.
It did not solve the distributed browser-like traffic.
It was tempting.
The suspicious traffic had a remarkably consistent Chrome/150 signature.
But blocking it globally would have violated one of the basic principles we wanted the system to follow:
A User-Agent is a signal, not authentication.
The same applied to several other tempting rules.
We did not want to block requests merely because they had:
/basket/ URL
Each might be useful as a signal.
None was strong enough to become the security boundary.
We built a separate WooCommerce protection layer around the legacy GET add-to-cart flow.
The idea is straightforward.
A client should not be able to arrive from nowhere and immediately perform a legacy cart mutation.
A normal storefront visitor first establishes first-party context.
The cart request then carries short-lived proof that this context exists.
The proof is signed using HMAC:
$issued_at = time();
$nonce = wp_generate_password( 20, false, false );
$payload = 'v1.' . $issued_at . '.' . $nonce;
$signature = hash_hmac(
'sha256',
$payload,
wp_salt( 'auth' )
);
The resulting proof is stored in a secure first-party cookie with properties including:
HttpOnly
Secure
SameSite=Lax
The proof expires after five minutes.
Before WooCommerce processes a legacy add-to-cart operation, the guard verifies the signature and age.
Conceptually:
Legacy ?add-to-cart request
|
v
Valid short-lived proof?
/ \
no yes
| |
403 v
WooCommerce
This does not attempt to determine whether the client is human.
That is an important distinction.
A sufficiently capable automation system can obtain the proof.
The mechanism instead changes the economics and state requirements of the attack while preventing blind, stateless cart mutations.
There was a practical WordPress problem.
A heavily cached storefront page may be served without executing the PHP code that would normally issue a proof cookie.
So proof issuance cannot depend solely on uncached page execution.
We exposed a cache-safe proof endpoint:
/wp-json/wccg/v1/proof
A request to that endpoint returns the signed first-party cookie and explicitly disables caching.
Example:
{"issued":true}
The storefront can establish the proof while full-page caching remains enabled.
Again, this is not CAPTCHA.
It is not proof of humanity.
It is proof of state.
The store already uses Redis as its WordPress external object cache.
Rather than creating another Redis connection, the protection layer can use the normal WordPress object-cache API.
That gives us inexpensive counters for signals such as:
global cart mutations per minute
requests with valid proof
requests without proof
rejected cart mutations
proof issuance velocity
That becomes useful if the automation adapts.
For example, if the bot eventually learns:
GET /wp-json/wccg/v1/proof
GET /?add-to-cart=1234
then possession of a proof is no longer sufficiently discriminating.
At that point Redis-backed behavioral and velocity signals can become the next layer.
Security controls should evolve with the attacker instead of pretending one rule will remain perfect forever.
This is where the project became more interesting.
At Zologic, we are also building UCPReady, our WooCommerce implementation of the Universal Commerce Protocol.
The same production store under attack is intentionally exposed to legitimate AI commerce systems.
Its UCP manifest is available at:
/.well-known/ucp
and it exposes agent-commerce transports including:
REST
MCP
Embedded
That means a simplistic security solution could have "fixed" the bot problem by breaking the future commerce infrastructure we actually wanted to preserve.
So the cart protection explicitly exempts the UCP and REST infrastructure.
The result is two separate trust paths.
Browser
|
v
first-party proof
|
v
legacy ?add-to-cart
|
v
WooCommerce
php
AI agent
|
v
/.well-known/ucp
|
+--> REST
|
+--> MCP
|
+--> Embedded checkout
They do not need to pretend to be the same thing.
That separation is important.
We did not want to rely only on our own testing.
UCP Checker independently evaluates UCP storefronts and runs live checks against manifests and declared transports.
House of Parfum currently receives:
UCP Status: Verified
Grade: A
Score: 100/100
Agent Discovery: 100
UCP Conformance: 100
Capability Coverage: 100
Transports:
- REST
- MCP
- Embedded
The store is also currently listed among the highest-scoring stores on the UCP Checker leaderboard.
Earlier testing by UCP Checker was particularly valuable to us because their UCP Playground exposed real interoperability problems that controlled development environments had not.
That is exactly what standards-based engineering needs: independent implementations testing each other's assumptions.
There is an emerging mistake I expect many commerce sites to make.
They will see AI bots, scrapers and abusive automation and respond with increasingly broad blocking rules.
Eventually legitimate agents will be blocked too.
The opposite mistake is equally dangerous: exposing everything because "AI agents need access."
The correct model is more nuanced.
An AI crawler fetching public discovery metadata is not the same operation as an anonymous client mutating a shopping cart.
A UCP agent using a defined commerce protocol is not the same thing as a scraper hammering legacy WooCommerce URLs.
Security policy should reflect those differences.
Our architecture now looks roughly like this:
Internet
|
+------------+-------------+
| |
Recognized bots Commerce clients
| |
v v
Bot policy layer +------+------+
| |
Legacy Web UCP/API
| |
v v
Cart proof Protocol
guard endpoints
| |
WooCommerce <------+
Different clients.
Different capabilities.
Different trust boundaries.
After enforcement became active, we measured the real traffic again.
For the post-enforcement sample:
All add-to-cart requests: 36
403 blocked: 35
302 allowed: 1
Then we isolated the distributed Chrome/150 pattern:
Chrome/150 add-to-cart requests: 30
403: 30
302: 0
30 out of 30 were rejected.
The rotating IP addresses continued.
The User-Agent continued.
The requests continued.
But changing IP addresses no longer helped because IP identity was no longer the security boundary.
At the same time, we verified that legitimate UCP infrastructure remained online:
200 /.well-known/ucp
200 /wp-json/ucpready/v1/openapi.json
200 /wp-json/ucpready/v1/mcp-tools.json
That was the outcome we wanted.
Not:
Block more bots.
But:
Stop unauthorized state mutation while preserving legitimate machine commerce.
The most useful lesson from this incident was not a particular WordPress hook or HMAC function.
It was changing the question.
When an attacker can rotate through thousands of addresses, asking:
Is this IP a bot?
has diminishing value.
Instead ask:
Has this client established the state and authority required to perform this operation?
That principle extends well beyond WooCommerce.
Protect the operation.
Define the trust boundary.
Make expensive state transitions require context.
Keep machine-readable public interfaces intentionally accessible.
And treat legitimate agents as first-class clients instead of forcing them to masquerade as browsers.
We are continuing to develop these ideas at Zologic, including WooCommerce bot protection, agent-aware access controls, UCPReady, MCP/REST commerce integrations, and security architectures that distinguish legitimate AI agents from abusive automation.
If your WooCommerce store is dealing with:
add-to-cart abuse
we are interested in looking at the problem.
The solution should not be another giant IP blacklist.
It should fit the way your store actually works.
If you're seeing similar traffic, contact Zologic and send us a sample of the pattern you're dealing with. We can help investigate it and design the appropriate protection or agent-commerce integration.
This article describes a real production incident. IP addresses and infrastructure observations are treated as network evidence only; they do not establish the identity or intent of the underlying operator.