Any antibot challenge that does not rely on a third party server?

Hello, is anyone using antibot challenge that does not rely on recaptcha or similar spyware?
I have tried to ask HivePress AI and Lumo, but both gives non working code which does not report error and avoids registration entirely.

Sample **non-**working code:

// 1. Add math challenge to registration form
add_filter(
	'hivepress/v1/forms/user_register',
	function( $form ) {
		// Generate new random numbers each time form renders
		$num1 = rand( 5, 15 );
		$num2 = rand( 5, 15 );
		
		// Store answer in transient with UUID key
		$challenge_id = wp_generate_uuid4();
		set_transient( 'hp_math_answer_' . $challenge_id, $num1 + $num2, HOUR_IN_SECONDS );
		
		$form['fields']['math_challenge_id'] = [
			'type'  => 'hidden',
			'value' => $challenge_id,
		];
		
		$form['fields']['math_challenge'] = [
			'label'       => sprintf( 'What is %d + %d?', $num1, $num2 ),
			'type'        => 'text',
			'required'    => true,
			'_order'      => 100,
		];
		
		return $form;
	},
	1000
);

// 2. Validate answer
add_filter(
	'hivepress/v1/forms/user_register/errors',
	function( $errors, $form ) {
		if ( isset( $_POST['math_challenge'], $_POST['math_challenge_id'] ) ) {
			$challenge_id = sanitize_text_field( $_POST['math_challenge_id'] );
			$user_answer = (int) $_POST['math_challenge'];
			$correct_answer = get_transient( 'hp_math_answer_' . $challenge_id );
			
			if ( false === $correct_answer ) {
				$errors[] = 'Math challenge expired. Please refresh the page.';
			} elseif ( $user_answer !== $correct_answer ) {
				$errors[] = 'Incorrect answer. Please try again.';
			} else {
				// Success - delete transient
				delete_transient( 'hp_math_answer_' . $challenge_id );
			}
		}
		
		return $errors;
	},
	1000,
	2
);

Working code, BUT reliant on a third party service and WP plugin (unwanted in this case) is:

// Add hCaptcha to user register form
add_filter(
	'hivepress/v1/forms/user_register',
	function( $form ) {
		$form['footer'] = '<div id="my-hcaptcha-box">' . do_shortcode('[hcaptcha]') . '</div>' . hivepress()->helper->get_array_value( $form, 'footer' );
		return $form;
	},
	100
);

// Throw error if hCaptcha is missing
add_filter(
	'hivepress/v1/forms/user_register/errors',
	function( $errors, $form ) {
		$result = \HCaptcha\Helpers\API::verify_request();		

		if ( null !== $result ) {
			$errors[] = 'Please solve the hCaptcha.';
		}		
		
		return $errors;
	},
	100,
	2
);

(above hcaptcha code is from this tutorial)

if you do not have working solution and looking for one, are you able to tweak some of these codes to work? Yes, i know this is a weak protection, but i am unable to come up with better as a non-developer. But the idea may be using Javascript in the challenge or some drag/drop action.

Hi @obtrusive170,

Edit: working code below! :slight_smile:

Cheers,
Chris :victory_hand:

2 Likes

Thanks for sharing the solution.

We plan to add hCaptcha support in future updates – it seems to guarantee user privacy, but if a self-hosted challenge is required, then a fully custom implementation is needed for sure. The simplest but not fully effective solution may be adding a “honeypot” checkbox to the registration form via the hivepress/v1/forms/user_register hook.

1 Like

Thank you for a good news that more private captcha (hcaptcha) is planned and @ChrisB PHP snippet worked (one needs to remove 1st line of it) to add working math challenge to a registration form. Hive AI have been able to modify it to work also optionally on a reviews form. You had to use some clever prompt or right AI model, I am surprised it works after many failed attempts of mine.

Hi @obtrusive170,

Yes, plugins like Code Snippets automatically add the <?php part, so you don’t need to include the first line of the snippet I shared above.

I’m not sure if you seen my other topic, but in case you’re interested, I’ve managed to come up with a working Cloudflare Turnstile bridge for HivePress.

“Cloudflare Turnstile is a free, privacy-first CAPTCHA alternative that verifies human users without forcing them to solve puzzles like selecting traffic lights or typing blurry text. It uses invisible, background JavaScript challenges—like proof-of-work tests and browser API probing—to analyze client behavior and distinguish real users from automated bots.”

You can also read more about Cloudflare Turnstile here.

I hope this helps!

Cheers,
Chris :victory_hand:

3 Likes

There may be issue with the mentioned PHP snippet in case one is using caching plugin like Litespeed cache. Reloading page (F5) does not reload math (is cached). But caching can not be excluded, since #user_register_modal can be accessed on any page, not a specific one (so no specific URI can be excluded at /wp-admin/admin.php?page=litespeed-cache#excludes . Caching plugin staff mentions “You can exlcude a URL, but not a user_register_modal, unless that module all starting with the same URLs”.

If it is possible, please how can I use
define( 'DONOTCACHEPAGE', true );

inside above linked snippet code?

I have added it like this:

function hp_add_math_challenge( $form ) {
	define( 'DONOTCACHEPAGE', true );

...

if ( HP_MATH_CAPTCHA_REGISTRATION ) {
	define( 'DONOTCACHEPAGE', true );

...

if ( HP_MATH_CAPTCHA_REVIEWS ) {
	define( 'DONOTCACHEPAGE', true );

but suspect that it disables caching for all pages, because /wp-admin/admin.php?page=litespeed-toolbox#log_viewer shows 💵 ❌ forced no cache [reason] DONOTCACHEPAGE const despite i have not clicked/displayed a reg. form, just loaded /listings/ page. Full log entry is here.

Fluent Snippets plugin developer says:

This would need to be handled with a custom condition/hook depending on how that modal is rendered by your theme or plugin. If the modal is loaded dynamically with JavaScript, PHP may not be able to detect the #user_register_modal directly at page render time. In that case, you may need to enqueue a small JS snippet or hook into the plugin/theme action that outputs the modal.

Any idea on a working solution to exclude reg. form math from caching please?

Hi @obtrusive170,

Edit: working code below! :slight_smile:

Cheers,
Chris :victory_hand:

Hi @obtrusive170,

I’ve just emailed you the latest version AI produced, and it seems to be working on my end anyway. Let me know how you get on.

For anyone else interested in the code:

<?php
/**
 * Cache-safe Math CAPTCHA for HivePress registration and reviews forms.
 * No third-party services. Toggle each form via the constants below.
 *
 * v2 changes:
 * - No admin-ajax.php. Snippet managers (FluentSnippets, WPCode, etc.) often
 *   don't load "frontend" snippets during admin-ajax requests, because
 *   admin-ajax.php is admin context (is_admin() === true). That caused a
 *   400 Bad Request: the endpoint was simply never registered. The challenge
 *   is now served from a plain frontend request (?hp_math_challenge=1),
 *   which is exactly the context where this snippet provably runs.
 * - Cache-safe by design: the page HTML contains no randomness, so full page
 *   caching (LiteSpeed, FlyingPress, WP Rocket, Cloudflare...) stays enabled
 *   everywhere. The challenge request itself is a POST (page caches only
 *   serve GET/HEAD), sends nocache_headers(), and defines DONOTCACHEPAGE
 *   scoped to that single request only.
 * - No closing PHP tags anywhere, for maximum snippet-manager compatibility.
 */

// -------------------------------------------------------------------------
// Configuration: enable/disable math CAPTCHA for each form.
// -------------------------------------------------------------------------
define( 'HP_MATH_CAPTCHA_REGISTRATION', true );
define( 'HP_MATH_CAPTCHA_REVIEWS', true );

// -------------------------------------------------------------------------
// 1. Placeholder fields — no randomness at render time, so pages are
//    identical for every visitor and 100% cache-safe.
// -------------------------------------------------------------------------
function hp_add_math_challenge_fields( $form ) {

	$form['fields']['math_challenge_id'] = [
		'type' => 'hidden',
	];

	$form['fields']['math_challenge'] = [
		'label'      => 'Spam check',
		'type'       => 'text',
		'required'   => true,
		'_order'     => 100,
		'attributes' => [
			'autocomplete' => 'off',
		],
	];

	return $form;
}

// -------------------------------------------------------------------------
// 2. Frontend challenge endpoint: POST yoursite.com/?hp_math_challenge=1
//    Runs on `init`, before any template or cache logic, and exits with
//    JSON. DONOTCACHEPAGE is defined HERE — scoped to this request only,
//    so it never disables caching for normal pages.
// -------------------------------------------------------------------------
function hp_math_challenge_endpoint() {

	if ( empty( $_GET['hp_math_challenge'] ) ) {
		return;
	}

	if ( ! defined( 'DONOTCACHEPAGE' ) ) {
		define( 'DONOTCACHEPAGE', true );
	}
	nocache_headers();

	$num1         = wp_rand( 5, 15 );
	$num2         = wp_rand( 5, 15 );
	$challenge_id = wp_generate_uuid4();

	set_transient( 'hp_math_answer_' . $challenge_id, (string) ( $num1 + $num2 ), HOUR_IN_SECONDS );

	wp_send_json_success(
		[
			'id'       => $challenge_id,
			'question' => sprintf( 'What is %d + %d?', $num1, $num2 ),
		]
	);
}
add_action( 'init', 'hp_math_challenge_endpoint' );

// -------------------------------------------------------------------------
// 3. Frontend loader: fetches a challenge only when a form containing the
//    math_challenge field is opened (modal link click), focused, or linked
//    directly via a URL hash. Generic — covers registration AND reviews.
// -------------------------------------------------------------------------
function hp_math_challenge_footer_script() {

	$endpoint = wp_json_encode( add_query_arg( 'hp_math_challenge', '1', home_url( '/' ) ) );

	$script = <<<JS
<script>
(function () {
	'use strict';

	var ENDPOINT = {$endpoint};

	function initChallenge(form) {
		if (!form || form.dataset.mathLoaded === '1') {
			return;
		}

		var answerField = form.querySelector('input[name="math_challenge"]');
		var idField     = form.querySelector('input[name="math_challenge_id"]');

		if (!answerField || !idField) {
			return;
		}

		form.dataset.mathLoaded = '1';

		fetch(ENDPOINT, { method: 'POST', credentials: 'same-origin', cache: 'no-store' })
			.then(function (r) {
				if (!r.ok) { throw new Error('HTTP ' + r.status); }
				return r.json();
			})
			.then(function (res) {
				if (!res || !res.success) { throw new Error('Bad challenge response'); }

				idField.value = res.data.id;

				// Always set the placeholder, and also update the label
				// if we can find it.
				answerField.placeholder = res.data.question;

				var wrapper = answerField.closest('.hp-form__field');
				var label   = wrapper ? wrapper.querySelector('label') : null;

				if (label) {
					var span = label.querySelector('span');
					(span || label).textContent = res.data.question;
				}
			})
			.catch(function () {
				// Allow a retry on the next interaction.
				form.dataset.mathLoaded = '';
			});
	}

	function initInside(container) {
		if (!container) { return; }
		var input = container.querySelector('input[name="math_challenge"]');
		if (input) {
			initChallenge(input.closest('form'));
		}
	}

	function targetFromHash(hash) {
		if (!hash || hash.length < 2) { return null; }
		try {
			return document.querySelector(hash);
		} catch (e) {
			return null;
		}
	}

	// Any modal trigger link (e.g. #user_register_modal, #review_submit_modal).
	document.addEventListener('click', function (e) {
		var link = e.target.closest('a[href^="#"], a[href*="#"]');
		if (!link) { return; }
		var href = link.getAttribute('href') || '';
		var idx  = href.indexOf('#');
		if (idx === -1) { return; }
		initInside(targetFromHash(href.slice(idx)));
	});

	// Focus anywhere inside a form containing the challenge field.
	document.addEventListener('focusin', function (e) {
		var form = e.target.closest('form');
		if (form && form.querySelector('input[name="math_challenge"]')) {
			initChallenge(form);
		}
	});

	// Direct links such as https://example.com/#user_register_modal
	function initFromLocation() {
		initInside(targetFromHash(window.location.hash));
	}
	if (document.readyState === 'loading') {
		document.addEventListener('DOMContentLoaded', initFromLocation);
	} else {
		initFromLocation();
	}
})();
</script>
JS;

	echo $script; // phpcs:ignore WordPress.Security.EscapeOutput
}
add_action( 'wp_footer', 'hp_math_challenge_footer_script', 100 );

// -------------------------------------------------------------------------
// 4. Validation — consumes the transient on success. Hardened: missing
//    fields now fail instead of silently passing (a bot that omitted the
//    fields from its POST could previously bypass the check entirely).
// -------------------------------------------------------------------------
function hp_validate_math_challenge( $errors, $form ) {

	if ( ! isset( $_POST['math_challenge'], $_POST['math_challenge_id'] ) ) {
		$errors[] = 'Spam check missing. Please refresh the page and try again.';
		return $errors;
	}

	$challenge_id = sanitize_text_field( wp_unslash( $_POST['math_challenge_id'] ) );
	$user_answer  = (int) sanitize_text_field( wp_unslash( $_POST['math_challenge'] ) );
	$stored       = get_transient( 'hp_math_answer_' . $challenge_id );

	if ( false === $stored ) {
		$errors[] = 'Spam check expired. Please refresh the page and try again.';
	} elseif ( $user_answer !== (int) $stored ) {
		$errors[] = 'Incorrect answer. Please try again.';
	} else {
		// Correct — consume the transient to prevent replay.
		delete_transient( 'hp_math_answer_' . $challenge_id );
	}

	return $errors;
}

// -------------------------------------------------------------------------
// Registration form.
// -------------------------------------------------------------------------
if ( HP_MATH_CAPTCHA_REGISTRATION ) {
	add_filter( 'hivepress/v1/forms/user_register', 'hp_add_math_challenge_fields', 1000 );
	add_filter( 'hivepress/v1/forms/user_register/errors', 'hp_validate_math_challenge', 1000, 2 );
}

// -------------------------------------------------------------------------
// Reviews form.
// -------------------------------------------------------------------------
if ( HP_MATH_CAPTCHA_REVIEWS ) {
	add_filter( 'hivepress/v1/forms/review_submit', 'hp_add_math_challenge_fields', 1000 );
	add_filter( 'hivepress/v1/forms/review_submit/errors', 'hp_validate_math_challenge', 1000, 2 );
}

I hope this helps!

Cheers,
Chris :victory_hand:

1 Like

Yes, this math challenge worked both for the registration and review forms when Litespeed cache was enabled. Thank you for help.

1 Like

I’m glad to hear that it’s working for you, too! :slight_smile:

Happy HivePress-ing! :honeybee:

Cheers,
Chris :victory_hand: