Repository navigation
Bot protection
Bot protection stops automated sign-ups, spam comments, password-reset floods, scripted logins and fake orders on the public forms of WordPress and WooCommerce. It has two layers. A honeypot guards registration, lost password and comments and needs no outside service. Cloudflare Turnstile guards login, registration, lost password, comments and checkout once its keys are in wp-config.php.
The module is off by default. Every Turnstile switch is on by default but does nothing until both Turnstile keys are set.
| Module ID | bots |
| Default | Off |
| Available | Always |
| WP-CLI | None |
| Since | 0.1.0 |
| Form | Honeypot | Turnstile |
|---|---|---|
Login: wp-login.php, login forms made with wp_login_form(), the WooCommerce My Account login |
No | Yes |
Registration: wp-login.php, the WooCommerce My Account registration |
Yes | Yes |
Lost password: wp-login.php, the WooCommerce lost password form |
Yes | Yes |
| Comments and WooCommerce product reviews, from visitors who are not logged in | Yes | Yes |
| WooCommerce checkout: classic, block, and orders sent straight to the Store API | No | Yes |
Every check is tied to the handler of one form: wp-login.php, WooCommerce's form handler, wp-comments-post.php and the Store API checkout route. Nothing else is checked:
- wp-admin, including Send password reset for a user
- WP-CLI and cron
- REST API requests with application passwords
- forms of other plugins, such as contact forms
There is no honeypot on login, because password managers fill in and send login forms at once, and none on checkout, because browser autofill fills hidden fields and the Store API cannot carry the field.
Comments are checked only for visitors who are not logged in. The checkout is checked for everyone, logged-in customers included.
On registration, lost password and comment forms, SafeHouse adds a block that is placed off-screen and hidden from screen readers. It holds a text field labelled "Leave this field empty" (shouse_url) and a hidden field shouse_proof. On the first key press, tap, click or input inside the form, SafeHouse's script copies a proof value into shouse_proof.
A submission passes only when the trap field is present and empty and the proof matches. Bots that fill every field, and bots that post the form without running its script, fail. The message is:
This looks like an automated submission. Please make sure JavaScript is on and try again.
The proof is the same for every visitor, so pages from a full-page cache keep working. It is derived from the site's AUTH_KEY and AUTH_SALT. If you change those keys, cached pages carry the old proof and every honeypot form on them fails until you clear the page cache.
Turnstile is Cloudflare's check that a visitor is a person. SafeHouse loads Cloudflare's script only on pages that show a protected form, or the checkout when Turnstile on checkout is on.
The widget runs in Cloudflare's interaction-only appearance: most visitors never see it. It becomes visible only when Cloudflare needs the visitor to act, inside the form (on the block checkout, above the order button), and then takes the width of the form.
If someone sends the form before Turnstile has finished, the browser holds the form for up to 15 seconds while it waits for the token, then sends it anyway and the server decides.
The server then asks Cloudflare whether the token is valid, with a timeout of 5 seconds. A token passes only when:
- Cloudflare confirms it,
- it was made for this form (each form has its own action name, such as
shouse_login, so a token from one form cannot be used on another), and - Cloudflare reports that the widget ran on the host name of the site's WordPress address or site address.
The visitor's IP address is sent along only when SafeHouse knows the real visitor address (see Behind Cloudflare or a proxy).
A failed check shows:
The anti-bot check did not pass. Please wait for it to finish and try again.
A browser that cannot load Turnstile, for example because an extension blocks challenges.cloudflare.com, sends the placeholder token unreachable. Cloudflare rejects it, so that visitor gets the message above, whatever When Cloudflare cannot be reached says.
On the block checkout, the token travels in the HTTP header X-SHouse-Turnstile of the Store API request. SafeHouse checks every POST to the Store API checkout route (/wc/store/checkout and /wc/store/v1/checkout, including /checkout/<order ID> for paying an existing order), whatever the query string says. On the classic checkout, SafeHouse checks the token only when WooCommerce found no other error in the order, so the single-use token is not spent on an order that fails anyway.
| Form | What happens |
|---|---|
wp-login.php login, registration, lost password |
The form is shown again with the message. |
| WooCommerce login, registration, lost password | WooCommerce shows the message as an error notice. |
| Classic checkout | The checkout shows the message as an error. No order is placed. |
| Block checkout and the Store API | HTTP 403 with the code shouse_bots and the message. No order is placed. |
| Comments and reviews | WordPress's comment error page with the message and HTTP status 403. The comment is not saved. |
A failed Turnstile check on a wp-login.php login also counts as a failed login for Login limits.
On SafeHouse → Modules → Bot protection, select Details.
| Setting | Default | What it does |
|---|---|---|
| Honeypot on registration, lost password and comments | On | "An invisible trap field plus proof that a person used the form in a browser. Needs no outside service. Visitors with JavaScript turned off cannot register, reset a password or comment." |
| Turnstile on login | On | "wp-login.php, login forms made with wp_login_form() and the WooCommerce login form." |
| Turnstile on registration | On | "WordPress and WooCommerce My Account registration." |
| Turnstile on lost password | On |
wp-login.php and the WooCommerce lost password form. |
| Turnstile on comments and reviews | On | "For visitors who are not logged in." |
| Turnstile on checkout | On | "Classic and block checkout, including orders sent straight to the Store API. Express payment buttons outside the checkout page (such as Apple Pay or Google Pay on product pages) cannot pass this check, so turn it off on sites that use them." |
| When Cloudflare cannot be reached | Let the submission through and log it | The other choice is Block the submission. See Choose what happens when Cloudflare cannot be reached. |
The Turnstile switches have no effect until both keys are in wp-config.php. While the module runs, Details shows which state Turnstile is in:
| Message | Meaning |
|---|---|
| "Turnstile is off: add SHOUSE_TURNSTILE_SITE_KEY and SHOUSE_TURNSTILE_SECRET_KEY to wp-config.php. The honeypot works without them." | One or both keys are missing. |
| "Turnstile is off: wp-config.php has Cloudflare test keys, which always give the same answer and are not allowed on a production site." | See Use test keys on staging. |
| "Turnstile is on, with keys from wp-config.php." | Turnstile runs on the forms whose switches are on. |
Turnstile needs a Cloudflare account, which can be a free one. The site does not have to use Cloudflare for its DNS or traffic.
-
In the Cloudflare dashboard, go to Turnstile.
-
Select Add widget.
-
In Widget name, enter a name you recognize, for example the site's domain.
-
In Hostname management, add the host name of the site, for example
example.com. If WordPress and the site use different host names in Settings → General, add both.SafeHouse refuses a token when Cloudflare reports a host name that is not the host of the site's WordPress address or site address.
-
In Widget mode, choose Managed, the mode Cloudflare recommends.
-
Select Create.
-
Copy the site key and the secret key.
-
Open
wp-config.phpand add these two lines above the line that says to stop editing, with your own keys:define( 'SHOUSE_TURNSTILE_SITE_KEY', 'YOUR_SITE_KEY' ); define( 'SHOUSE_TURNSTILE_SECRET_KEY', 'YOUR_SECRET_KEY' );
-
Go to SafeHouse → Modules and switch on Bot protection.
-
Select Details and check the Turnstile switches. Turn off Turnstile on checkout if the shop has express payment buttons outside the checkout page (see Shops with express payment buttons).
-
Select Save Changes.
Details shows "Turnstile is on, with keys from wp-config.php." On the Integrations tab, the Cloudflare row shows Turnstile as on.
To test, open wp-login.php in a private browser window and log in. Usually you see nothing of Turnstile: the interaction-only appearance keeps the widget hidden unless Cloudflare needs you to act.
Cloudflare publishes test keys that always give the same answer, for example the always-pass pair:
define( 'SHOUSE_TURNSTILE_SITE_KEY', '1x00000000000000000000AA' );
define( 'SHOUSE_TURNSTILE_SECRET_KEY', '1x0000000000000000000000000000000AA' );SafeHouse treats any key that starts with 1x, 2x or 3x followed by at least 15 zeros as a test key. It refuses test keys on a production site, so that a staging wp-config.php copied to production cannot switch the check off quietly. With test keys on production, Turnstile is off and Details says why.
A site counts as production when wp_get_environment_type() returns production. WordPress returns that when WP_ENVIRONMENT_TYPE is not set. To use test keys on a staging or development copy, declare its type in that copy's wp-config.php:
define( 'WP_ENVIRONMENT_TYPE', 'staging' );The values local, development and staging all allow test keys. With test keys, SafeHouse skips the form and host name checks, because test tokens carry fixed values.
When Cloudflare cannot be reached decides what happens when the server cannot ask Cloudflare about a token:
- the request to Cloudflare fails or takes longer than 5 seconds
- Cloudflare answers with an HTTP status other than 200, or with something that is not JSON
- Cloudflare answers that the secret key is missing or wrong, or reports an internal error
A wrong secret key is treated like an outage on purpose: a typo in wp-config.php then follows this setting instead of locking everyone out of wp-login.php.
| Choice | What happens |
|---|---|
| Let the submission through and log it (default) | The form is accepted as if the check had passed. The activity log gets a bot_blocked warning with the reason cloudflare unavailable. |
| Block the submission | The visitor sees "The anti-bot check is unavailable right now. Please try again in a few minutes." The log gets the same warning. |
The help text on the settings page sums it up: "Applies only when this server cannot reach Cloudflare, or Cloudflare rejects the secret key. A visitor whose browser blocks Turnstile is still asked to try again."
Choose Block the submission only if you would rather lose real sign-ups, comments and orders during a Cloudflare outage than let bots through. Logins are blocked too, so with this choice and a broken secret key nobody can log in through wp-login.php while Turnstile on login is on. If that happens to you, see Recover from a lockout.
Important
Express payment buttons outside the checkout page, such as Apple Pay or Google Pay on product or cart pages, send orders without a Turnstile token. With Turnstile on checkout on, every one of those orders is refused.
If the shop uses such buttons, turn off Turnstile on checkout. The same applies to headless shops and apps that send orders to the Store API checkout route without the X-SHouse-Turnstile header. The other forms stay protected.
- When Wordfence Login Security has its reCAPTCHA on, Turnstile on login and Turnstile on registration are locked with "Handled by Wordfence Login Security, skipped here." With WooCommerce active, this happens only when Wordfence Login Security's WooCommerce integration is also on. The honeypot on registration still runs. Set
SHOUSE_IGNORE_OVERLAPStotrueto run both. -
Login limits counts a failed Turnstile check on
wp-login.phpas a failed login. - Hardening's generic login error never replaces the Bot protection messages.
- A theme or plugin form that posts to
wp-login.php,wp-comments-post.phpor the WooCommerce form handler but does not run the standard WordPress or WooCommerce form hooks lacks the honeypot fields, so every submission from it fails. Turn the honeypot off, or use the standard forms.
- Settings in the
shouse_settingsoption. The keys stay inwp-config.php. - Activity log entries
bot_blocked, for example "Bot protection on comments: honeypot. Repeats are not logged for 10 minutes." The reason ishoneypot,turnstileorcloudflare unavailable(a warning; the others are info). One entry per form and reason every 10 minutes, so a bot flood cannot flood the log. - Transients
shouse_bots_*that hold that 10-minute pause.
Bot protection sets no cookies, has no tables and sends no e-mails.
- The honeypot proof is one fixed value per site, and there is no timing check. A bot that loads the page and copies the value from it passes the honeypot. Turnstile is the stronger layer.
- Visitors with JavaScript turned off cannot register, reset a password or comment while the honeypot is on, and fail every form where Turnstile is on.
- Turnstile is usually invisible because it runs in interaction-only mode. You cannot tell from looking at a form whether it is protected; check Details and the activity log.
- On the block checkout, the widget is placed only where the checkout block's order button area is in the page. A heavily changed checkout without it gets no widget, and orders then fail the check.
- Express payment buttons outside the checkout page and headless Store API clients cannot pass Turnstile on checkout.
- Forms of other plugins, such as contact forms, are not protected.
- Rotating
AUTH_KEYorAUTH_SALTbreaks honeypot forms on cached pages until the page cache is cleared.
These pages describe SafeHouse 0.2.0 and were last reviewed on 2026-10-07. · Repository · Releases · Changelog · Report a problem with these pages
Search every page and heading in the Pages box.
Start here
Modules
- All modules
- Security: Hardening · Login limits · Bot protection · Install lockdown
- Alerts: Change alerts · Plugin health · Vulnerability alerts
- Performance: LiteSpeed page cache · Cloudflare cache · Redis object cache
- WooCommerce: Omnibus price history
- Site tools: SMTP mail · Header and footer scripts · Duplicate posts and pages · Maintenance mode · Tweaks
Guides
- Behind Cloudflare or a proxy
- Running with Wordfence
- Configure in wp-config
- Deploy with git or CI
- Replacing other plugins
- Upgrade from WPHouse
Reference
Trust
Help