Card testing
How card-testing limits work
Where ChargeWarden checks payments (classic checkout, Store API, pay for order), what it counts, the default limits, and how it works with WooCommerce's own Store API rate limiter.
Last updated by the PluginLoom team
ChargeWarden checks every place a card can be tried, counts attempts and failed payments per shopper, and blocks a shopper for a while when they reach a limit. Blocked shoppers see a neutral message and no payment is attempted.
Where does ChargeWarden check?#
| Entry point | How it is reached | Hook ChargeWarden uses |
|---|---|---|
| Classic checkout | ?wc-ajax=checkout from the [woocommerce_checkout] page | woocommerce_after_checkout_validation |
| Checkout block and direct API calls | POST /wp-json/wc/store/v1/checkout | rest_request_before_callbacks |
| Pay for order page | /checkout/order-pay/{id}/ | woocommerce_before_pay_action |
| Store API pay for order | POST /wp-json/wc/store/v1/checkout/{id} | rest_request_before_callbacks |
| Add payment method (My account) | Saved-card form | woocommerce_add_payment_method_form_is_valid |
The Store API matters even if your store uses the classic checkout. WooCommerce's developer blog said on 18 December 2024 that it had seen "card attacks specifically targeting the Store API alone, even without the use of the Checkout block" (source (opens in a new tab)).
What counts as a failed payment?#
- Classic checkout and pay for order: the order was created and the gateway did not return a successful payment result by the end of the request.
- Store API: the response was a payment error (
woocommerce_rest_checkout_process_payment_errororwoocommerce_rest_payment_error) or the payment status wasfailureorerror. - Later failures: an order a gateway marks Failed afterwards, for example from a webhook, using the identifiers saved on the order at checkout.
Form mistakes such as a missing address are not counted, because no payment was attempted.
What is counted, and how?#
| Identifier | How it is formed | Stored as |
|---|---|---|
| IP address | From the IP source you choose (direct connection by default) | Keyed hash, plus a masked display value such as 203.0.113.x |
| Network | The /24 for IPv4 or the /64 for IPv6 | Keyed hash |
Lower case, +tag removed, Gmail dots removed | Keyed hash | |
| Browser | A SHA-256 digest of coarse browser properties, made by the checkout script | Keyed hash |
| Card | BIN (first 6 digits) and last 4, only when the gateway exposes them | Keyed hash |
| Session | The WooCommerce session or Store API cart token | Keyed hash |
The keyed hash is HMAC-SHA256 with a secret unique to your site, so ChargeWarden can count repeats without storing who the shopper is.
What are the default limits?#
| Rule | Counts | Maximum | Window | Then |
|---|---|---|---|---|
| ip_failure | Failed payments per IP | 5 | 10 minutes | Block 1 hour |
| subnet_failure | Failed payments per network | 12 | 10 minutes | Block 1 hour |
| email_failure | Failed payments per email | 4 | 1 hour | Block 24 hours |
| device_failure | Failed payments per browser | 5 | 1 hour | Block 6 hours |
| card_failure | Failed payments per card | 3 | 1 hour | Block 24 hours |
| session_failure | Failed payments per session | 3 | 10 minutes | Block 1 hour |
| ip_attempt | Attempts per IP | 15 | 10 minutes | Challenge, or block 15 minutes |
| subnet_attempt | Attempts per network | 40 | 10 minutes | Challenge, or block 15 minutes |
Change any of them in Settings > Card testing > Limits. The Strict profile lowers each maximum and doubles block times; Relaxed doubles each maximum.
When is risk "elevated"?#
A shopper is elevated when any failure count is at least halfway to its limit, or an attempt count is past 60% of its limit, or attack mode is on. Elevated shoppers are asked for a challenge when you have a provider set up. Without a provider they continue until they reach a limit.
How does it work with WooCommerce's own rate limiting?#
WooCommerce core has a Store API rate limiter, documented at Store API rate limiting (opens in a new tab) (checked 24 October 2026):
- It is off by default. When on, the default is 25 requests per 10 seconds, set with the
woocommerce_store_api_rate_limit_optionsfilter. - Since WooCommerce 9.6, Settings > Advanced > Features > Rate limit Checkout limits
POST /wc/store/v1/checkoutto 3 requests per 60 seconds. - It tracks the user ID when logged in, otherwise the IP address, and since 9.8 accepts your own identifier through
woocommerce_store_api_rate_limit_id. - It answers HTTP 429 and fires
woocommerce_store_api_rate_limit_exceeded.
ChargeWarden does not replace it. The wizard offers to turn on "Rate limit Checkout", and ChargeWarden records every woocommerce_store_api_rate_limit_exceeded hit as "Rate limited (WooCommerce)" on the dashboard. Core has no rate limit on the classic checkout, which ChargeWarden covers.
Which gateways are counted?#
All of them, unless you list a gateway ID in Settings > Card testing > Gateways whose failures are not counted. Blocks still apply to excluded gateways. For example, list bankbridge_ach if you do not want failed bank payments to count toward card-testing limits.
Was this page helpful?