자체 방문 통계는 항상 수집됩니다. 사용자가 공개 페이지를 어떻게 이용하는지 파악하는 데 쓰이는 Google Analytics(로그인한 워크스페이스는 제외)는 동의한 경우에만 사용됩니다. 자세한 내용은 개인정보 처리방침을 참고하세요.

CAPTCHA API 선택 방법: 유형 목록부터 직접 소유한 시스템에서의 검증 루프까지

직접 소유한 시스템과 공식 테스트 메커니즘 안에서 유형, 파라미터, 결과 형태를 검증합니다. 토큰 수락, 서버 측 검증, 비즈니스 작업, 비용을 분리해 반환값을 검증 성공으로 오인하지 않도록 합니다.

CAPTCHA를 연동할 때 가장 위험한 오판은 이름을 잘못 고르는 경우보다 "결과를 얻은 것"을 "검증 통과"로 취급하는 경우입니다. 클라이언트가 비어 있지 않은 토큰을 받는 것, 검증 서비스가 그 토큰을 수락하는 것, 비즈니스 로직이 작업을 허용하는 것은 서로 다른 세 가지 사건입니다. 첫 번째만 검증하면 폼이 백엔드에서 거부될 수 있고, 첫 번째를 곧바로 세 번째로 취급하면 내부 권한 검사를 우회할 위험이 있습니다.

이 가이드는 직접 소유한 시스템에서의 개발과 테스트를 대상으로 합니다. 챌린지 제품을 식별하는 방법, EveryInfra 유형의 현재 파라미터와 응답 형식을 읽는 방법, 테스트 구성을 격리하는 방법, 실패 시 시스템이 올바른 상태에서 멈추는지 검증하는 방법을 다룹니다. 공식 테스트 환경과 명시적으로 승인된 테스트 범위에도 적용됩니다. 이 글은 실제 제3자 계정, 결제, 권한, 리스크 챌린지를 조작하는 절차는 제공하지 않습니다.

무엇을 먼저 테스트할지 정하기

검증에 성공하면 폼이 데이터를 저장하고 실패하면 쓰기를 차단하는지만 확인하려는 것이라면, 챌린지 제품이 제공하는 공식 테스트 메커니즘을 우선 사용하십시오. 이 메커니즘을 쓰면 CI를 실행할 때마다 실제 챌린지를 풀지 않고도 결과를 제어할 수 있습니다. 이 단계에서 다른 API를 도입해도 테스트 가치는 늘지 않고, 별도로 검수해야 하는 의존성만 하나 더 생깁니다.

EveryInfra의 CAPTCHA 기능을 평가해야 한다면 테스트를 두 개의 독립적인 질문으로 나누십시오. API 입력과 출력이 합의된 형태와 일치하는지, 그리고 승인된 환경에서 반환값이 올바르게 처리되는지입니다. 두 질문의 증거는 분리해 보관하고, 한쪽의 성공으로 다른 쪽을 대신하지 마십시오. 선택의 출발점은 목록에 나열된 유형 수가 아니라 테스트 과제입니다.

제품은 외형이 아니라 연동 구성으로 식별하기

제품, 모드, 도메인, 사용 사례는 자체 페이지의 연동 코드, 백엔드 구성, 해당 공식 문서로 확인하십시오. 눈에 보이는 체크박스만으로는 요청 유형을 판단할 수 없으며, 같은 제품의 다른 모드를 임의로 바꿔 써서도 안 됩니다. 페이지 소유자, 테스트 허용 범위, 사용하는 테스트 구성, 예상 결과를 담은 명확한 테스트 기록을 남기십시오.

직접 관리하지 않는 승인된 환경이라면 파라미터는 해당 환경 관리자가 제공하거나 승인된 문서를 통해 얻어야 합니다. 맥락이 부족하면 중단하고 관리자에게 확인을 요청하십시오. 프로그램이 비슷한 유형을 추측하거나 필드를 지어내게 해서는 안 됩니다. 페이지에서 공개 식별자를 볼 수 있다고 해서 임의의 자동화 작업이 허용되는 것은 아닙니다.

예를 들어 Turnstile의 hostname 구성은 프로토콜, 포트, 경로가 없는 도메인 이름을 받으며, 상위 도메인을 구성하면 하위 도메인도 포함됩니다. 테스트 환경에서는 잘못된 주소를 통과시키려고 구성을 완화하지 말고, 관리자가 확인한 범위를 사용해야 합니다. 이는 요청의 website_url 필드와는 다른 개념입니다.

유형, 가용성, 파라미터, 결과 형태 읽기

아래는 CAPTCHA 해결 작업을 생성하지 않는 무료 목록 조회이며 인증이 필요 없습니다. 이 예시는 파라미터와 응답 형식을 비교하기 위해 두 가지 유형만 선택하며, 두 챌린지 모두에 대한 작업을 요구하는 것은 아닙니다. 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였습니다. 당시 두 유형 모두 사용 가능했습니다. 이는 목록상의 선언일 뿐, 실제 챌린지에서의 성공을 증명하지는 않습니다.

따라서 결과 핸들러는 최상위 토큰을 무조건 가져오거나 임의의 객체를 문자열로 강제 변환하지 말고, 검증된 유형과 solution 형식에 따라 분기해야 합니다. optional_params에 있는 파라미터는 카탈로그가 선택 사항으로 선언했다는 의미일 뿐입니다. 어떤 조합이 성공하는지는 현재 오류와 가용성에 달려 있으며, 필수 비즈니스 제약을 제거해 억지로 성공시켜서는 안 됩니다.

선택한 항목과 함께 관측 시각을 저장하십시오. available이 false이면 제출하지 마십시오. 필드 누락, 잘못된 유형, 조회 실패는 알 수 없는 상태로 보고 중단하십시오. true라 하더라도 가용성은 관측 시점에 해당 기능을 시도할 수 있었다는 뜻일 뿐, 이후 작업의 성공을 보장하지 않습니다.

공식 테스트 메커니즘을 프로덕션 구성과 분리하기

Cloudflare Turnstile은 통과, 실패, 이미 사용된 토큰을 시뮬레이션하는 테스트용 sitekey와 secret을 제공합니다. 테스트용과 프로덕션용 토큰이나 키는 절대 섞어 써서는 안 됩니다. Google reCAPTCHA FAQ는 v2 테스트 키와 v3 테스트 구성을 따로 안내하며, v3 테스트 환경의 점수를 실제 트래픽에서의 리스크 판단 성능으로 간주해서는 안 됩니다.

sitekey는 페이지에서 사용하는 공개 식별자이고, secret은 백엔드 검증용 키입니다. 테스트 구성은 명시적인 환경 설정에서 읽어야 하며, 요청이 테스트라고 주장한다는 이유만으로 항상 통과하는 동작으로 전환해서는 안 됩니다. 프로덕션 시작 시 구성 출처와 환경 일관성을 확인하고, 로그에는 필요한 감사 상태만 남기되 secret, 전체 토큰, 원본 요청 객체는 출력하지 마십시오.

비즈니스 코드에서는 검증 서비스를 최소한의 기능만 노출하는 인터페이스로 감싸십시오. 비즈니스 로직은 명시적인 검증 결과를 받고, 테스트에서는 합성 결과를 주입할 수 있습니다. 실제 외부 검증은 여전히 서버 측에서 구현합니다. 주입 메커니즘은 테스트 빌드나 서버 구성으로만 제어해야 하며, 사용자가 선택할 수 있는 요청 파라미터로 노출해서는 안 됩니다. 이는 애플리케이션 설계 권장 사항이며, EveryInfra가 제공하는 자동 기능이 아닙니다.

토큰 수락, 서버 검증, 비즈니스 로직을 분리하기

Turnstile은 공식적으로 서버 측에서 Siteverify를 호출하도록 요구하며, 프런트엔드 위젯만으로는 완전한 보호가 되지 않습니다. 공식 문서에 따르면 토큰은 5분간 유효하고 한 번만 검증할 수 있습니다. 토큰을 오래 캐시하거나 재사용할 수 있는 자격 증명으로 취급하지 말고, 네트워크 장애를 복구하려고 같은 값을 끝없이 재시도하지 마십시오.

서버 검증이 성공하더라도 비즈니스 로직은 세션 확인, 리소스 권한, 입력 검증, 중복 제출 제어를 그대로 수행해야 합니다. CAPTCHA는 계정 자격 증명도, 거래 승인도 아닙니다. 후속 단계가 실패하면 비즈니스 오류를 그대로 유지하고, 이를 "CAPTCHA 실패"로 바꿔 새 작업을 무한히 생성하지 마십시오.

검증 세부 사항은 제품 간에 그대로 옮길 수 없습니다. Google reCAPTCHA 백엔드 검증 문서는 토큰이 2분간 유효하고 한 번만 사용할 수 있다는 규칙과 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을 명시적으로 요구하므로 검사기도 action을 강제합니다. 모든 챌린지 제품이 동일한 필드를 반환한다고 가정하지 마십시오. 검사를 완화한 범용 함수를 재사용하지 말고, 제품마다 검증 규칙을 따로 구현하십시오.

성공, 거짓 성공, 문자열 성공 값, 도메인 오류, action 누락, 빈 응답, 서버 구성 누락을 각각 테스트하십시오. 그다음 승인된 테스트 연동 안에서 페이지부터 자체 백엔드까지 전체 경로를 검증하십시오. 오프라인 함수를 통과했다는 것은 해당 분기 판단만 증명할 뿐, 네트워크 검증, 프로덕션 구성, 리스크 식별 효과까지 증명하지는 않습니다.

실패 위치에 따라 다음 단계 정하기

  • 입력 단계: 유형 불일치, 필수 값 누락, 지원되지 않는 파라미터 조합. 먼저 파라미터를 수정하고, 동일한 요청을 반복하지 마십시오.
  • 기능 단계: 목록 정보를 알 수 없거나 현재 사용할 수 없음. 테스트를 중단하고 관측 내용을 기록하되, 챌린지 제품을 임의로 바꾸지 마십시오.
  • 호출 단계: 명시적 실패와 타임아웃을 따로 기록하십시오. 결과를 받지 못했다고 해서 서버가 요청을 실행하지 않았다는 뜻은 아니므로, 먼저 요청 연관 식별자를 보존하십시오.
  • 검증 단계: 자체 백엔드가 결과를 거부함. 안전한 오류 분류와 해당 테스트 케이스를 기록하고, 수락 기준을 완화하는 대신 환경, 도메인, action을 확인하십시오.
  • 비즈니스 단계: 검증은 통과했지만 입력, 권한, 중복 제출 때문에 거부됨. CAPTCHA가 풀리지 않은 것처럼 처리하지 말고 비즈니스 워크플로로 처리하십시오.

비용도 이 계층별로 대사해야 합니다. 현재 EveryInfra 구현에는 해결 실패 후의 회계상 환불 분기가 있지만, 클라이언트 측 검증 실패나 비즈니스 거부가 게이트웨이의 해결 실패 판정과 같은 것은 아닙니다. 프런트엔드의 빨간 오류 문구만 보고 환불이나 예약 크레딧 복구를 추정하지 말고, 호출 응답과 접근 가능한 과금 기록으로 동일한 요청을 대사하십시오. 이 글은 실제 실패에 대한 새로운 잔액 복구 사례를 제시하지 않으며, 이후의 모든 거부가 회계 상태를 바꾼다고 약속하지도 않습니다.

검토 가능한 테스트 기록 남기기

유용한 검수 기록에는 승인된 환경, 제품 모드, 관측 시각, 테스트 구성 범주, 입력 구조, 응답 구조, 검증 판단, 비즈니스 판단, 과금 판단이 담겨야 합니다. 가능하면 실제 내용은 생략하고, 마스킹한 연관 ID로 계층별 결과를 연결하십시오. 다루지 않은 영역은 명시적으로 문서화해, 이후 팀이 "목록 조회 성공"을 완전한 검수로 오인하지 않게 하십시오.

공식 테스트 메커니즘으로 자체 성공·실패 흐름을 검증하고, 필요한 API 분기를 평가한 뒤에야 승인된 범위 안에서 검증을 확대하십시오. CAPTCHA 유형을 선택하는 기준은 이름의 유사성이나 값이 있는 응답이 아니라, 명확한 입력 조건, 해석 가능한 반환 형태, 올바른 백엔드 판단, 그리고 비즈니스 경계를 벗어나지 않는 실패 처리입니다.

참고 LF-15