Checkout Bouncer

Checkout Bouncer documentation

This Checkout Bouncer documentation covers everything you need to install the plugin, get your Google reCAPTCHA v3 keys, run the Checkout Scan and read the report. Most stores are protected in about ten minutes. If you get stuck, the troubleshooting section near the bottom covers the four problems people actually hit.

Requirements

Checkout Bouncer runs on any normal WooCommerce store. There is nothing to compile and no external service to sign up for beyond free Google reCAPTCHA keys.

WhatMinimumNotes
WordPress6.2 or newerSingle site or multisite.
PHP7.4 or newerPHP 8.1 or 8.2 recommended for speed.
WooCommerce7.0 or newerHPOS (custom order tables) supported.
Checkout typeClassic shortcode or block checkoutBoth are covered. Six page builders are detected.
Google reCAPTCHAv3 site key and secret keyFree. v2 checkbox keys will not work.
Outbound HTTPSTo google.comYour server verifies each token with Google.

Checkout Bouncer protects the checkout. It is not a firewall and not a malware scanner, and it does not touch login, registration, comments or contact forms.

Installing the plugin

Two ways in. Both end at the same place: WooCommerce → Checkout Bouncer in your admin menu.

From the WordPress.org directory

This is the easiest route and gives you one-click updates.

  1. Go to Plugins → Add New.
  2. Search for Checkout Bouncer.
  3. Click Install Now, then Activate.

By uploading the zip

Use this if you have the zip file in hand.

  1. Go to Plugins → Add New → Upload Plugin.
  2. Choose the zip and click Install Now.
  3. Click Activate Plugin.
  4. If you prefer SFTP, unzip into wp-content/plugins/ and activate from the Plugins screen.

Activating the plugin does not block anything on its own. Nothing changes for your customers until you add keys and tick the enable box.

Getting Google reCAPTCHA v3 keys

reCAPTCHA v3 is free from Google. It takes about three minutes. You need a Google account and the domain your shop runs on.

  1. Open the reCAPTCHA admin console and sign in with your Google account.
  2. Click the + button to register a new site.
  3. Give it a label you will recognise later, such as your shop name.
  4. Under reCAPTCHA type, choose Score based (v3). This is the important step. Checkout Bouncer uses v3 only, so v2 checkbox or v2 invisible keys will fail verification.
  5. Under Domains, add your shop domain without https:// and without a trailing slash, for example example.com. Google treats subdomains as covered, so example.com also covers www.example.com and shop.example.com. If your staging site sits on a different domain, add that too, or register a separate key pair for it.
  6. Accept the reCAPTCHA Terms of Service and click Submit.
  7. Google shows you two keys. The site key is public and goes into your page. The secret key is private and is used server-side. Copy both.

Keep the secret key secret. Never paste it into a public support thread or a page template. If you think it has leaked, delete the key pair in the Google console and register a new one, then update the settings.

First run

Four steps. Do them in this order.

  1. Paste your keys. Go to WooCommerce → Checkout Bouncer and put the site key and secret key into the two fields. Save.
  2. Tick enable. Verification stays off until you switch it on. Leave the score threshold at 0.5 for now; that is Google’s own recommendation and it is the right starting point for almost every shop.
  3. Run the Checkout Scan. The scanner finds the checkout page your store actually uses, works out what renders it, hunts for duplicate or rogue checkout pages elsewhere on the site, checks whether the Store API checkout route is exposed, and flags settings that silently verify nothing.
  4. Read the report and act on it. Each finding comes with a one-click Protect or Block. Protect adds verification to a route that can carry a token. Block shuts a route that cannot. The scanner never offers to block your own active checkout.

Then place one real test order yourself. Open the events table afterwards and confirm you see a pass with a score against it. If you see a pass, the wiring is correct and you are done.

Settings reference

Every setting, group by group, and what it changes for a real shopper. This is the part of the Checkout Bouncer documentation worth bookmarking.

1. Keys

1. Keys
SettingWhat it does
Site keyYour public reCAPTCHA v3 site key. Printed into the page so the browser can request a token.
Secret keyYour private key. Used server-side to verify each token with Google. Never sent to the browser.
Enable protectionThe master switch. With this off, nothing is verified and nothing is blocked.

2. Scoring and behaviour

2. Scoring and behaviour
SettingWhat it doesSuggested
Score thresholdreCAPTCHA v3 returns a score from 0.0 (almost certainly a bot) to 1.0 (almost certainly a person). Orders scoring below your threshold are blocked.0.5
Fail openDecides what happens when Google cannot be reached or the verification call times out. Fail open lets the order through so sales keep flowing. Fail closed blocks it.On, unless you are under active attack
Strict browser errorsWhen on, a browser-side reCAPTCHA error is treated as a failure instead of being tolerated. Tightens things up at the cost of the occasional false block on odd browsers and privacy extensions.Off to start
Token refreshreCAPTCHA tokens expire after a couple of minutes. Refresh keeps a fresh token on the page so shoppers who take their time filling in the address form are not rejected for a stale token.On
Script loading scopeSitewide by default, because v3 scores a visitor on their whole-site behaviour and that is what Google recommends. You can narrow it to checkout screens only if you would rather the script did not load elsewhere; expect slightly less reliable scores.Sitewide
BadgeShow or hide the floating reCAPTCHA badge. If you hide it, Google’s terms require you to credit reCAPTCHA in your privacy policy or near the checkout button.Your call

3. Protected checkout screens

Tick the routes an order can enter your store through. Each one is a separate code path in WooCommerce, and a plugin that only covers the first of them leaves the rest wide open.

3. Protected checkout screens
ScreenWhy it matters
Classic shortcode checkoutThe traditional [woocommerce_checkout] page. What classic captcha plugins cover.
Block checkout (Store API)The block checkout submits through the WooCommerce Store API and never fires the classic checkout hooks, so a captcha that only hooks the classic form never sees those orders. Checkout Bouncer registers Store API endpoint data so the token travels with the request.
Pay for orderThe pay page behind an order-pay link. A favourite of card testers because it is a clean, repeatable form.
Add payment methodOptional. Covers the my-account screen where a saved card is added, which is a cheap way for a bot to test a card without placing an order.

4. Payment methods

Target verification by gateway, using your store’s real gateway list rather than a guessed one. Three modes:

  • All gateways is the default, and the right answer for most shops.
  • Include verifies only the gateways you tick, which is useful if card testing is hitting one card gateway and you do not want to touch anything else.
  • Exclude verifies everything except the gateways you tick; it is handy for bank transfer, cash on delivery or an invoice gateway used by trade customers.

5. Blocking

Some routes cannot carry a token at all. You cannot protect those; you can only close them.

5. Blocking
SettingWhat it does
Block the Store API checkout routeHard-blocks the Store API checkout endpoint with a 404 or a 403, your choice. Use this when your store runs a classic checkout and has no legitimate need for the Store API checkout route. If you use the block checkout, or a headless or mobile front end, leave this alone and protect the route instead.
404 or 403404 tells a scanner the route does not exist, which is the quieter answer. 403 is explicit about refusal. 404 is usually the better deterrent.
Block rogue checkout pagesCloses duplicate checkout pages the scanner found elsewhere on the site (old drafts, builder copies, a staging page someone published by accident). Your own active checkout is never offered for blocking.

6. Order-rate throttle

Scoring catches most bots. The throttle catches the rest by limiting what a single IP address can do in a short window.

6. Order-rate throttle
LimitWhat it countsSensible start
Max orders per hourOrders placed from one IP in a rolling hour.Well above your busiest genuine customer
Max failed payments per 15 minutesDeclined payments from one IP. This is the card-testing signal: a real customer retries twice, a bot retries fifty times.Low. This one does the work.
Max distinct billing emails per hourHow many different billing email addresses one IP uses. Catches a bot cycling through generated addresses.Low, unless you sell from shared offices
Monitor modeLogs what would have been blocked without blocking anything. Run this for a day or two first, look at the events table, then set your real limits with evidence instead of guesswork.On for the first 48 hours

If your store sits behind a proxy or Cloudflare, read the proxy section before you turn the throttle on. Without the right configuration every visitor looks like the same IP and they all share one bucket.

7. Bypass

7. Bypass
SettingWhat it does
Staff role bypassChosen roles skip verification and the throttle. Use it so a shop manager placing phone orders all afternoon is never throttled.
IP allowlistAddresses that always skip checks. Accepts IPv4, IPv6 and CIDR ranges, one per line. Put your office and warehouse here. Keep it short, because every entry is a hole you have chosen to leave open.

8. Logging

Every decision lands in the events table: pass, fail or block, with the score and the reason. The dashboard rolls that up into pass, fail and block counts and a list of your top block reasons, so you can see at a glance whether you are being probed. Export the events to CSV when you need to hand evidence to your payment provider or work through a pattern in a spreadsheet.

The Checkout Scanner explained

Most stores are not protected the way their owner believes. The scan exists to tell you the truth. It answers four questions: which page is really your checkout, what actually renders it, what other checkout-shaped pages exist on this site, and is the Store API checkout route reachable.

Rendering detection covers the block checkout, the classic shortcode, and six page builders: Elementor, Divi, WPBakery, Beaver Builder, Bricks and Oxygen. It matters because a builder can wrap your checkout in a way that changes which hooks fire.

Finding: unprotected active checkout

Your real checkout is live but the matching screen is not ticked. Click Protect. This is the single most common finding and the one that actually costs money.

Finding: Store API checkout route exposed

The block checkout’s submission route is reachable. If you use the block checkout, or anything headless, choose Protect so the token travels with the request. If your store runs a classic checkout and nothing else needs that route, choose Block.

Finding: duplicate or rogue checkout page

A second page on your site renders a checkout. Usually an old draft, a builder copy, or a test page someone published years ago. Bots find these because they crawl your sitemap. Confirm it is not something you rely on, then choose Block.

Finding: configuration verifies nothing

The plugin looks switched on but no request would ever be checked: keys missing, no screens ticked, or a gateway rule that excludes everything you actually sell with. Fix the setting the finding names. This is the silent failure the scan exists to catch.

Re-run the scan after any theme change, builder change, or WooCommerce update. New pages appear and old ones come back from the dead.

Behind a proxy or Cloudflare

Read this before you turn the throttle on if there is anything sitting in front of your web server.

Checkout Bouncer uses REMOTE_ADDR for the visitor IP by default, and it does that on purpose. Forwarded-IP headers can be set by anyone, so trusting one blindly would let an attacker forge a fresh IP on every request and walk straight past the throttle and the allowlist.

The trade-off is that if you sit behind Cloudflare, a load balancer, a CDN or an Nginx reverse proxy, REMOTE_ADDR is the proxy, not the shopper. Every visitor then looks like the same address and your whole store shares one throttle bucket, so a busy hour can throttle real customers while a bot slips through as part of the crowd.

Naming the header your proxy sets

The fix is to opt in explicitly, naming the header your proxy sets. Put this in a small site plugin or your child theme’s functions.php:

// Tell Checkout Bouncer which header carries the real visitor IP.
// Only set this if the header is written by a proxy YOU control and
// visitors cannot reach your origin server directly.

// Cloudflare:
add_filter( 'checkout_bouncer_trusted_proxy_header', function () {
 return 'HTTP_CF_CONNECTING_IP';
} );

// Generic reverse proxy or load balancer that sets X-Forwarded-For:
// return 'HTTP_X_FORWARDED_FOR';

// Some hosts use X-Real-IP instead:
// return 'HTTP_X_REAL_IP';

Lock the back door as well. If your origin server is still reachable on its raw IP address, an attacker can bypass Cloudflare entirely and set the header themselves. Restrict your origin to your proxy’s addresses at the firewall, then trust the header.

To check whether it worked, place a test order and look at the IP recorded against the event. If it is your own address, you are set. If it is the same address as every other event in the table, the header is still wrong.

Troubleshooting

Start with the events table. It records the decision and the reason for every checkout attempt, which turns most of these from a mystery into a two-minute fix.

Shoppers see a “missing token” error

The checkout submitted without a reCAPTCHA token attached. Almost always one of these:

  • The reCAPTCHA script is not loading on that page. If you narrowed the script scope to checkout screens only and your checkout is rendered by a page builder, the plugin may not recognise the screen. Set the scope back to sitewide.
  • An optimisation plugin is deferring, combining or delaying the script. Exclude the Google reCAPTCHA script and Checkout Bouncer’s own script from JavaScript combining, deferral and “delay until interaction”.
  • A page cache is serving a stale checkout. Exclude the checkout and cart from full-page caching, which WooCommerce recommends anyway.
  • The token expired. Turn token refresh on so a long fill-in does not go stale.
  • A browser extension or corporate network blocks google.com. Rare but real. If it is a handful of customers rather than a pattern, turn strict browser errors off.

Test in a private window with all your caching and optimisation plugins temporarily off. If it works there, the culprit is one of them.

When scores or blocks look wrong

Scores are consistently too low

If genuine orders are scoring at 0.3 or below, reCAPTCHA is not seeing enough of the visit to judge it.

  • Set the script scope back to sitewide. v3 scores whole-site behaviour. If it only ever meets the visitor at the checkout, it has one page of evidence and it scores cautiously. This is the number one cause.
  • Check the keys are v3. v2 keys registered against the same domain will not score properly.
  • Check the domain list in the Google console matches the domain customers actually browse, including the www variant.
  • Give it traffic. A brand new key pair scores conservatively for the first day or two until Google has a baseline for your site.

Do not fix low scores by dropping the threshold to 0.1. Choosing a threshold from your own traffic covers what to do instead. That is the same as switching the plugin off. Fix the cause first, then tune.

When the plugin blocks the wrong people

Real customers are being blocked

Open the events table and read the block reason. It tells you which of two very different problems you have.

  • Blocked on score. Lower the threshold a step at a time, 0.5 to 0.4 to 0.3, and watch the pass and block counts on the dashboard after each change. Read the low-score entry above first, because a scope problem is usually the real cause.
  • Blocked on throttle means your limits are too tight, or you are behind a proxy and everyone shares one bucket. Turn monitor mode on, read the section on proxies, then set limits from what you actually see.
  • Blocked on a browser error. Turn strict browser errors off.

For known good customers (a trade account, your own warehouse, a customer on a shared office connection), add their address to the IP allowlist rather than loosening the rules for everyone.

When verification fails or the log stays empty

Google is unreachable, or verification times out

Your server verifies each token by calling Google. If that call fails, fail-open decides what happens: on, and the order goes through; off, and it is blocked.

  • Check your host allows outbound HTTPS to google.com. Some locked-down or firewalled hosts do not by default.
  • Check any security plugin that filters outbound requests.
  • Keep fail-open on for normal trading. Losing real sales to a network blip costs more than the handful of bot orders that slip through during it.
  • Turn fail-open off only while you are under active attack, and turn it back on afterwards.
Block checkout orders are not appearing in the log at all

This is the classic block-checkout blind spot, and it is exactly what Checkout Bouncer exists to close. The block checkout submits through the WooCommerce Store API and never fires the classic checkout hooks, so if the block checkout screen is not ticked in settings, those orders are never seen.

Run the Checkout Scan. It will tell you which renderer your checkout actually uses and offer a one-click Protect for the right route. Then place a test order and confirm a pass appears in the events table.

Developer hooks

Seven filters cover the customisation people ask for, and this part of the Checkout Bouncer documentation lists each one. Put your code in a small site plugin so a theme update cannot wipe it.

8. Logging
FilterWhat it controls
checkout_bouncer_trusted_proxy_headerWhich server header holds the real visitor IP. Empty by default, so REMOTE_ADDR is used. Set it only if you sit behind a proxy you control.
checkout_bouncer_skipSkip verification for a request entirely. Use for a specific integration, a known internal flow, or a scripted order.
checkout_bouncer_error_messageThe message a blocked shopper sees. Change it to give people a phone number or a way to reach you.
checkout_bouncer_should_blockThe final block decision. Your last word before an order is stopped or allowed.
checkout_bouncer_bypass_rolesThe list of roles that bypass checks. Add custom staff roles the settings screen does not know about.
checkout_bouncer_soft_errorsWhich error conditions are treated as soft, meaning logged and tolerated, instead of hard failures.
checkout_bouncer_throttle_exemptExempt a request from the order-rate throttle while still verifying its score.
// Change the message a blocked shopper sees.
add_filter( 'checkout_bouncer_error_message', function ( $message ) {
 return 'We could not verify this order. Please call us on 01234 567890 and we will take it over the phone.';
} );

// Let a custom staff role bypass checks.
add_filter( 'checkout_bouncer_bypass_roles', function ( $roles ) {
 $roles[] = 'warehouse_manager';
 return $roles;
} );

Several of these filters pass extra context as later arguments. Because add_filter only hands you the arguments you ask for, the one-argument callbacks above are safe as written; raise the accepted-args count when you want the context. The exact signatures are documented in the plugin source next to each apply_filters call.

When the Checkout Bouncer documentation does not cover it

Export your events to CSV and send it with your question. The block reasons usually make the answer obvious in a minute or two. The plugin is free and fully functional on WordPress.org. There is no paid tier to buy today.

See which doors into your checkout are standing open