フロントエンド側

<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
<div class="cf-turnstile" data-sitekey="サイトキー" data-callback="onTurnstileSuccess" data-expired-callback="onTurnstileExpired" data-error-callback="onTurnstileError" data-language="ja"></div>
<script>
  let turnstileToken = null;

  function onTurnstileSuccess(token) {
    console.log("✅ Turnstile 成功:", token);
    turnstileToken = token;
    document.getElementById("submit-btn").disabled = false;
  }

  function onTurnstileExpired() {
    console.log("⏳ Turnstile 期限切れ");
    turnstileToken = null;
    document.getElementById("submit-btn").disabled = true;
  }

  function onTurnstileError() {
    console.log("❌ Turnstile エラー");
    turnstileToken = null;
    document.getElementById("submit-btn").disabled = true;
  }

  function isTurnstileCompleted() {
    return !!turnstileToken;
  }
</script>

サーバー側

$data = [];
$data[ 'secret' ] = 'シークレットキー';
$data[ 'response' ] = $_POST[ 'cf-turnstile-response' ];
$data = http_build_query( $data, '', '&' );
$curl = curl_init();
curl_setopt_array( $curl, [
  CURLOPT_URL => 'https://challenges.cloudflare.com/turnstile/v0/siteverify',
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_POST => true,
  CURLOPT_POSTFIELDS => $data,
] );
$res = curl_exec( $curl );
$err = curl_error( $curl );
curl_close( $curl );
if ( $err || ! isset( $_POST[ 'cf-turnstile-response' ] ) || $_POST[ 'cf-turnstile-response' ] === '' ) {
  exit;
}
カテゴリー: All