Skip to content

Instantly share code, notes, and snippets.

@joseconti
Created October 1, 2026 12:21
Show Gist options
  • Select an option

  • Save joseconti/557af26a260e0c85e875d78e69bb84cb to your computer and use it in GitHub Desktop.

Select an option

Save joseconti/557af26a260e0c85e875d78e69bb84cb to your computer and use it in GitHub Desktop.
Stop SPam/Splog WooCOmmerce Checkout
<?php
/**
* WooCommerce Store API checkout protection.
*
* Requires a signed first-party cookie before allowing a Store API checkout.
* This helps reject bots posting directly to /wc/store/v1/checkout without
* affecting normal WooCommerce customer registration.
*/
defined( 'ABSPATH' ) || exit;
/**
* Cookie name used by the checkout protection.
*/
const JC_CHECKOUT_COOKIE = 'jc_checkout_protection';
/**
* Minimum age, in seconds, before the cookie is accepted.
*
* A very small delay rejects many direct automated requests without making
* legitimate customers wait during checkout.
*/
const JC_CHECKOUT_MIN_AGE = 2;
/**
* Maximum lifetime of the protection cookie.
*/
const JC_CHECKOUT_MAX_AGE = DAY_IN_SECONDS;
/**
* Creates the signed protection value.
*
* @param int $timestamp Timestamp when the token was generated.
* @param string $random Random token component.
* @return string
*/
function jc_checkout_create_token( $timestamp, $random ) {
$payload = $timestamp . '|' . $random;
$signature = hash_hmac(
'sha256',
$payload,
wp_salt( 'auth' )
);
return $payload . '|' . $signature;
}
/**
* Sets a first-party protection cookie for frontend visitors.
*
* @return void
*/
function jc_checkout_set_protection_cookie() {
if (
is_admin() ||
wp_doing_ajax() ||
wp_doing_cron() ||
headers_sent()
) {
return;
}
if ( isset( $_COOKIE[ JC_CHECKOUT_COOKIE ] ) ) {
return;
}
$timestamp = time();
$random = wp_generate_password( 32, false, false );
$value = jc_checkout_create_token( $timestamp, $random );
setcookie(
JC_CHECKOUT_COOKIE,
$value,
array(
'expires' => $timestamp + JC_CHECKOUT_MAX_AGE,
'path' => COOKIEPATH ? COOKIEPATH : '/',
'domain' => COOKIE_DOMAIN,
'secure' => is_ssl(),
'httponly' => true,
'samesite' => 'Lax',
)
);
/*
* Make the cookie available during the current PHP request too.
*/
$_COOKIE[ JC_CHECKOUT_COOKIE ] = $value;
}
add_action( 'template_redirect', 'jc_checkout_set_protection_cookie', 1 );
/**
* Validates the checkout protection cookie.
*
* @return bool
*/
function jc_checkout_has_valid_protection_cookie() {
if ( ! isset( $_COOKIE[ JC_CHECKOUT_COOKIE ] ) ) {
return false;
}
$cookie = sanitize_text_field(
wp_unslash( $_COOKIE[ JC_CHECKOUT_COOKIE ] )
);
$parts = explode( '|', $cookie );
if ( 3 !== count( $parts ) ) {
return false;
}
list( $timestamp, $random, $signature ) = $parts;
if ( ! ctype_digit( $timestamp ) ) {
return false;
}
$timestamp = (int) $timestamp;
$age = time() - $timestamp;
if (
JC_CHECKOUT_MIN_AGE > $age ||
JC_CHECKOUT_MAX_AGE < $age
) {
return false;
}
$expected = hash_hmac(
'sha256',
$timestamp . '|' . $random,
wp_salt( 'auth' )
);
return hash_equals( $expected, $signature );
}
/**
* Blocks direct Store API checkout requests without a valid browser token.
*
* @param mixed $result REST pre-dispatch result.
* @param WP_REST_Server $server REST server instance.
* @param WP_REST_Request $request Current REST request.
* @return mixed|WP_Error
*/
function jc_checkout_protect_store_api( $result, $server, $request ) {
if (
'POST' !== $request->get_method() ||
'/wc/store/v1/checkout' !== $request->get_route()
) {
return $result;
}
if ( jc_checkout_has_valid_protection_cookie() ) {
return $result;
}
/*
* Log blocked attempts without storing checkout/customer data.
*/
if ( function_exists( 'wc_get_logger' ) ) {
wc_get_logger()->warning(
'Blocked Store API checkout request without a valid browser token.',
array(
'source' => 'jc-checkout-protection',
)
);
}
return new WP_Error(
'jc_checkout_protection_failed',
__(
'Unable to process the checkout request. Please reload the page and try again.',
'jc-checkout-protection'
),
array(
'status' => 403,
)
);
}
add_filter(
'rest_pre_dispatch',
'jc_checkout_protect_store_api',
10,
3
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment