当社独自のアクセス統計は常に有効です。Google Analyticsは、公開ページ(ログイン後のワークスペースは対象外)の利用状況を把握するためのもので、許可をいただいた場合のみ有効になります。詳しくは プライバシーポリシーをご覧ください。

CAPTCHA APIの選び方:タイプ一覧から自社システムでの検証ループまで

自社で所有するシステムと公式のテスト手段の範囲内で、タイプ、パラメータ、結果の形式を検証します。トークンの受理、サーバー側の検証、業務処理、コストを分けて扱い、戻り値を検証成功とみなさないようにします。

CAPTCHAを組み込む際に最も危険な判断ミスは、名前を選び間違えることよりも、「結果を取得できた」ことを「検証に合格した」とみなしてしまうことです。クライアントが空でないトークンを受け取ること、検証サービスがそのトークンを受理すること、ビジネスロジックが操作を許可することは、それぞれ別の事象です。1つ目だけを確認すると、フォームがバックエンドで拒否される可能性が残ります。1つ目をそのまま3つ目とみなすと、内部の権限チェックを迂回するおそれがあります。

このガイドは、自社で所有するシステムでの開発とテストを対象としています。チャレンジ製品の特定方法、EveryInfraの各タイプの現在のパラメータとレスポンス形式の読み方、テスト設定の分離、失敗時にシステムが正しい状態で停止することの確認方法を扱います。公式のテスト環境や、明示的に許可されたテスト範囲にも適用できます。本記事では、実在するサードパーティのアカウント、決済、権限、リスクチャレンジを操作する手順は扱いません。

最初に決めるべきテスト対象

目的が、検証成功後にフォームがデータを保存し、失敗時に書き込みをブロックすることの確認だけであれば、チャレンジ製品が提供する公式のテスト機構を優先してください。公式の機構なら結果を制御でき、CIを実行するたびに実際のチャレンジを解くことにもなりません。この段階で別のAPIを導入してもテスト上の価値はなく、個別の受け入れ確認が必要な依存関係が増えるだけです。

EveryInfraのCAPTCHA機能を評価する必要がある場合は、テストを、APIの入出力が合意した形式に合っているか、許可された環境で戻り値が正しく処理されるかという、2つの独立した問いに分けてください。それぞれの検証記録は分けて管理し、一方の成功をもって、もう一方も成功したとみなさないでください。選定の出発点は、一覧に掲載されたタイプの数ではなく、テストで確認すべき内容です。

見た目ではなく連携設定に基づく製品の特定

自社ページの連携コード、バックエンド設定、対応する公式ドキュメントを使って、製品、モード、ドメイン、ユースケースを確認してください。見た目のチェックボックスだけではリクエストのタイプは決まらず、同じ製品でもモードが異なれば任意に入れ替えるべきではありません。テスト記録は明確に残し、ページの所有者、テストで許可されている範囲、使用するテスト設定、期待される結果を記録してください。

自社で保守していない許可済みの環境では、パラメータは環境の保守担当者から提供を受けるか、許可されたドキュメントから取得する必要があります。コンテキストが不足している場合は作業を止め、保守担当者に確認を依頼してください。プログラムに似たタイプを推測させたり、フィールドを捏造させたりしてはいけません。ページ上で公開されている識別子が見えても、任意の自動アクションが許可されるわけではありません。

たとえば、Turnstileのホスト名設定は、プロトコル、ポート、パスを含まないドメイン名を受け付け、親ドメインを設定するとそのサブドメインも対象になります。テスト環境では、誤ったアドレスを通すために設定を緩めるのではなく、保守担当者が範囲を確認する必要があります。これはリクエストのwebsite_urlフィールドとは別のものです。

タイプ、利用可否、パラメータ、結果形式の確認

以下は、認証不要かつ無料で、CAPTCHAの解決タスクを作成しないタイプ一覧の読み取りです。この例では、パラメータとレスポンス形式を比較するために2つのタイプだけを選んでおり、両方のチャレンジを操作することを求めるものではありません。curlとjqが必要です。タイプ一覧の読み取りに失敗した場合は、まずタイプを確認する段階の問題に対処し、出力が空のまま業務リクエストに進まないでください。

読み取り専用のタイプ一覧
set -o pipefail
curl -fsS --max-time 30 'https://api.everyinfra.com/api/v1/captcha/types' \
  | jq -e '[.types[] |
      select(.type == "turnstile" or .type == "recaptcha_grid") |
      {type, available, required_params, optional_params, solution}
    ] | if length == 2 then . else error("expected types missing") end'

2026/9/4に公開情報を確認した時点では、turnstileの必須パラメータはwebsite_urlとwebsite_key、任意パラメータにはaction、cdata、page_dataがあり、solution.shapeはtoken、フィールドはtokenでした。recaptcha_gridの必須パラメータはbodyとquestionで、shapeはpoints、フィールドはobjectsでした。どちらも当時は利用可能でした。これは一覧上の宣言であり、実際のチャレンジで成功することの証明ではありません。

そのため、結果ハンドラーはトップレベルのトークンを無条件に取り出したり、任意のオブジェクトを文字列に変換したりするのではなく、確認済みのタイプと解答形式に基づいて分岐させてください。optional_paramsに含まれるパラメータは、カタログ上で任意と宣言されていることを示すにすぎません。ある組み合わせが成功するかどうかは現在のエラーと可用性によって決まるものであり、成功させるために本質的なビジネス上の制約を取り除くべきではありません。

選択したエントリーと合わせて、観測時のタイムスタンプを保存してください。availableがfalseの場合は送信しないでください。フィールドの欠落、型の不一致、読み取りの失敗は不明として扱い、処理を停止します。trueの場合でも、示されるのは観測時点でその機能を試行できたことだけであり、その後のタスクの成功を保証するものではありません。

公式テスト機構と本番設定の分離

Cloudflare Turnstileは、通過・失敗・使用済みのトークンをシミュレートするためのテスト用sitekeyとsecretを提供しています。テスト用と本番用のトークンやキーは決して混在させないでください。Google reCAPTCHAのFAQでは、v2のテストキーとv3のテスト設定がそれぞれ別に説明されています。v3のテスト環境でのスコアを、実トラフィックにおけるリスク判定の性能とみなさないでください。

sitekeyはページで使用される公開識別子で、secretはバックエンドでの検証用キーです。テスト設定は明示的な環境設定から読み込むべきであり、リクエストがテストだと主張しているだけで常に通過する挙動に切り替えてはいけません。本番環境の起動時に設定の取得元と環境の整合性を確認してください。ログには監査に必要な情報だけを残し、secret、トークン全体、生のリクエストオブジェクトは出力しないでください。

ビジネスコードからは、検証サービスを必要最小限のインターフェースを介して利用してください。ビジネスロジックは明示的な検証結果を受け取り、テストでは合成した結果を注入できるようにします。実際の外部検証は引き続きサーバー側で実装します。注入の仕組みはテストビルドまたはサーバー設定でのみ制御し、ユーザーが選択できるリクエストパラメータとして公開してはいけません。これはアプリケーション設計上の推奨事項であり、EveryInfraが自動的に提供する機能ではありません。

トークンの受け取り、サーバー検証、ビジネスロジックの切り分け

Turnstileでは、サーバー側からSiteverifyを呼び出すことが公式に必須とされており、フロントエンドのコンポーネントだけでは完全な保護になりません。公式ドキュメントでは、トークンの有効期間は5分で、検証できるのは1回のみと明記されています。トークンを長期間キャッシュできる認証情報や再利用可能な認証情報として扱わないでください。また、ネットワーク障害からの復旧を、同じ値での際限ないリトライに頼らないでください。

サーバーでの検証が成功した場合でも、ビジネスロジックではセッションチェック、リソースの権限確認、入力検証、重複送信の制御を実行する必要があります。CAPTCHAはアカウントの認証情報でも取引の承認でもありません。後続のステップが失敗した場合はビジネスエラーをそのまま保持し、「CAPTCHAの失敗」と言い換えて新しいタスクを際限なく生成しないでください。

検証の詳細は製品間で流用できません。Google reCAPTCHAのバックエンド検証ドキュメントでは、トークンは2分以内に1回のみ使用できるというルールと、timeout-or-duplicateなどのエラーが規定されており、Turnstileの5分という有効期間とは異なります。エラー処理は、個々の製品と実際の検証レスポンスに結び付けてください。

オフライン例によるバックエンド受け入れ条件の確認

以下は、自身が所有するテストページ向けの結果チェッカーです。このページではhostnameをlocalhost、actionをtestと明示的に設定しており、期待値はユーザーのリクエストではなく固定のサーバー設定から取得します。実際の連携では、受信したトークンについて、自社のサーバーから公式の検証エンドポイントを呼び出して検証結果を取得する必要があります。クライアントから送信された成功判定を鵜呑みにしてはいけません。

JavaScript:検証結果を合成(ネットワークリクエストなし)
function acceptOwnTestVerification(result, expected) {
  if (!expected || typeof expected.hostname !== "string" ||
      !expected.hostname.trim() || typeof expected.action !== "string" ||
      !expected.action.trim()) {
    throw new Error("server-side test configuration required");
  }
  if (!result || typeof result !== "object" || Array.isArray(result)) {
    return { accepted: false, reason: "invalid_response" };
  }
  if (result.success !== true) {
    return { accepted: false, reason: "verification_failed" };
  }
  if (result.hostname !== expected.hostname) {
    return { accepted: false, reason: "hostname_mismatch" };
  }
  if (result.action !== expected.action) {
    return { accepted: false, reason: "action_mismatch" };
  }
  return { accepted: true, reason: "verification_only" };
}

// all values are self-made test fixtures; no network requests, no tokens held or generated.
const expected = { hostname: "localhost", action: "test" };
const synthetic = { success: true, hostname: "localhost", action: "test" };
console.log(acceptOwnTestVerification(synthetic, expected));
// { accepted: true, reason: 'verification_only' }

この例はverification_onlyを出力します。トークンは発行せず、Siteverifyも呼び出さず、フォームも送信しません。このテスト設定で明示的に必須としているため、チェッカーはactionを検証します。すべてのチャレンジプロダクトが同一のフィールドを返すとは考えないでください。チェックを緩めた汎用関数を使い回すのではなく、プロダクトごとに検証ルールを個別に実装してください。

成功、successがfalseの場合、成功値が文字列の場合、ドメインエラー、actionの欠落、空のレスポンス、サーバー設定の欠落をそれぞれ個別にテストしてください。その後、許可されたテスト連携の範囲内で、ページから自社バックエンドまでの全経路を検証します。オフラインの関数が通っても証明されるのはその分岐判定だけであり、ネットワーク経由の検証、本番設定、リスク識別の有効性までは証明されません。

失敗箇所に応じた次の対応の決定

  • 入力フェーズ:型の不一致、必須値の欠落、サポートされていないパラメータの組み合わせ。まずパラメータを修正し、同一のリクエストを繰り返さないでください。
  • 機能フェーズ:一覧にない、または現在利用できない。テストを中止して観測内容を記録し、チャレンジプロダクトを安易に切り替えないでください。
  • 呼び出しフェーズ:明示的な失敗とタイムアウトを分けて記録します。結果を受信できなかったことは、サーバーが実行に失敗したことを意味しません。まずリクエストの相関IDを保持してください。
  • 検証フェーズ:自社のバックエンドが結果を拒否する。安全に記録できるエラー分類と対応するテストケースを記録し、受け入れ基準を緩めるのではなく、環境、ドメイン、アクションを確認してください。
  • ビジネスフェーズ:検証は通過したものの、入力、権限、重複送信が原因で拒否される。CAPTCHAが未解決のままだったかのように扱わず、ビジネスワークフローで対処してください。

コストについても、これらのレイヤー間で照合する必要があります。現在のEveryInfra実装には、解決失敗後に会計上の返金を行う分岐がありますが、クライアント側の検証失敗やビジネス上の拒否は、ゲートウェイによる解決失敗の判定と同義ではありません。フロントエンドに表示された赤いエラーテキストだけから返金や確保額の復元を推測せず、同一のリクエストについて、呼び出しのレスポンスと参照可能な課金記録を突き合わせてください。本記事では、実際の失敗に対する残高回復の新しいサンプルは紹介していません。また、後続の拒否がすべて会計上の状態を変更するとも保証していません。

レビュー可能なテスト記録の作成

有用な受け入れ記録には、許可された環境、プロダクトのモード、観測タイムスタンプ、テスト設定のカテゴリ、入力構造、レスポンス構造、検証の判定、ビジネス上の判定、課金の判定を詳しく記載します。実際の内容は可能な限り省略し、マスクした相関IDで各レイヤーの結果を紐付けます。カバーしていない領域を明記し、将来のチームが「一覧の読み取りに成功した」ことを受け入れ完了と誤解しないようにしてください。

公式のテスト機構を使って自社の成功フローと失敗フローを検証し、必要なAPI分岐を評価した後に限り、許可された範囲内で検証を広げてください。CAPTCHAの種類を選ぶ基準は、名前の類似性や何らかの値が返ることではなく、明確な入力条件、解釈可能なレスポンス形式、バックエンドによる正しい判定、業務上の範囲を逸脱しない失敗処理です。

参考資料 LF-15