문의 폼 봇은 클라우드플레어 턴스타일로 거른다. 사이트키는 브라우저, 시크릿은 서버다. 사이트베리파이로 300초 토큰을 한 번만 확인한다. 리캡차 이미지 퍼즐은 한국 모바일에서 자주 막힌다. 관리 모드가 기본이고 안 보이는 모드는 개인정보 약관이 따로다. 개발자, API, Next.js, 보안, CSP 기준으로 테스트 키와 호스트 제한만 본다.
문의 폼에 구글 리캡차를 붙였더니, 한국 휴대폰에서 이미지 퍼즐이 안 열린 적 있으실 겁니다. 클라우드플레어 턴스타일은 사진 고르기를 기본으로 두지 않습니다. 브라우저가 토큰을 만들고, 서버가 사이트베리파이로 한 번만 확인합니다. 토큰은 생성 뒤 300초이고, 같은 값은 두 번 통과하지 않습니다. 위젯만 붙이고 서버를 건너뛰면 보호가 아닙니다. 로컬 https 주소는 터널 글에, 사파리 쿠키는 세임사이트 글에 있습니다. 여기는 문의와 로그인 폼의 봇 칸만 봅니다.
12개 사이트를 돌리며 저는 파트너십 폼부터 리캡차를 뺐습니다. 숫자는 2026년 8월 클라우드플레어 턴스타일 문서 기준입니다.
문의 폼이 한국 모바일에서 막히면 어디부터 보나
리캡차 이미지 칸이 하얗게 남으면, 먼저 위젯 호스트가 한국 인앱 브라우저에서 열리는지 봅니다. 캡차 종류를 바꾸기 전에 그 칸부터 확인합니다.
구글 리캡차는 사진 퍼즐을 자주 띄웁니다. 카카오톡 인앱, 삼성 인터넷, 느린 엘티이에서 퍼즐 이미지가 비거나 체크 박스가 멈춘 날을 여러 번 봤습니다. 사용자는 스팸이 아니라 사람인데, 문의가 서버에 도착하지 않습니다. 턴스타일 공식 시작 문서는 전통 캡차 없이 방문자를 확인한다고 적습니다. 관리 모드는 위험도에 따라 체크 박스를 띄울 수는 있어도, 글자와 사진을 해독시키는 칸은 아닙니다.
턴스타일은 클라우드플레어 프록시가 없어도 됩니다. 문서가 다른 시디엔, 온프레미스, 멀티 클라우드에서도 위젯과 사이트베리파이만으로 동작한다고 밝힙니다. 버셀에만 올려 둔 Next.js 사이트에도 붙습니다. 제가 막힌 자리는 리캡차 스크립트가 한국 모바일에서 이미지를 못 받는 쪽이었습니다. 봇 점수 API를 더 파는 문제가 아니었습니다.
칸
사용자에게 보이는 것
서버가 할 일
언제 쓰나
없음
폼만
없음
내부망, 로그인 뒤에만 열리는 화면
구글 리캡차
체크 또는 이미지 퍼즐
구글 검증 엔드포인트
이미 구글 콘솔에 묶여 있고, 퍼즐이 한국에서 열리는 팀
턴스타일 관리
대부분 자동. 필요할 때만 체크
사이트베리파이 필수
문의, 파트너십, 로그인. 제가 기본으로 두는 칸
턴스타일 비가시
위젯이 안 보임
사이트베리파이 + 개인정보 약관 문구
디자인만 우선할 때. 약관 문장 없으면 쓰지 않음
터널로 로컬 주소를 여는 글과 자리를 섞지 않습니다. 그 글은 카카오 콜백 https 입니다. 여기는 폼이 봇인지 사람인지 가리는 칸입니다. 링크 미리보기가 내부망을 대신 여는 사고는 에스에스알에프 글입니다.
위젯이 초록이어도 끝이 아닙니다 | 브라우저는 토큰 문자열만 만듭니다. 공격자는 아무 문자열을 폼에 넣을 수 있습니다. 문서가 서버 검증을 필수라고 적습니다.
위젯 키와 시크릿은 어디에 나누나
사이트키는 에이치티엠엘에 넣고, 시크릿은 서버 환경 변수에만 둡니다. 같은 쌍을 브라우저와 서버에 동시에 노출하지 않습니다.
대시보드에서 위젯을 하나 만들면 사이트키와 시크릿이 한 쌍으로 나옵니다. 사이트키는 공개 값입니다. 시크릿은 사이트베리파이 요청에만 씁니다. 호스트 이름 허용 목록에 실제 도메인만 넣습니다. 문서는 프로덕션 사이트키에 localhost 를 넣지 말라고 권합니다. 로컬 시험은 더미 키를 씁니다. 더미 사이트키 1x00000000000000000000AA 는 항상 통과하는 보이는 위젯입니다. 더미 시크릿 1x0000000000000000000000000000000AA 는 더미 토큰만 받습니다. 운영 시크릿은 더미 토큰을 거절합니다.
개발, 스테이징, 운영 위젯을 나눕니다. 문서가 환경마다 다른 위젯을 쓰라고 적습니다. 이름도 로그인 폼, 문의 폼처럼 용도로 붙입니다. 호스트를 느슨하게 두면 다른 사이트에 사이트키를 심어 토큰을 만들 수 있습니다. 서버에서 호스트 이름을 한 번 더 대조하면 그 구멍이 줄어듭니다.
암시 렌더는 cf-turnstile 클래스만 있으면 됩니다. 제출 때 숨은 필드 cf-turnstile-response 가 채워집니다. 시크릿을 NEXT_PUBLIC_ 에 넣으면 번들에 나갑니다. 그 사고는 시크릿 체크 글과 같습니다. 사이트키만 공개 환경 변수로 둡니다.
사이트키는 에이치티엠엘에 둔다. 시크릿은 사이트베리파이 요청에만 쓴다
브라우저만 통과시키고 서버를 건너뛰는 실수
위젯이 성공해도 서버가 사이트베리파이를 부르지 않으면, 아무 문자열로 문의가 들어옵니다. 보호는 그 호출에서 끝납니다.
공식 검증 문서는 세 가지를 이유로 적습니다. 토큰은 위조될 수 있습니다. 생성 뒤 300초가 지나면 만료됩니다. 한 번 검증된 값은 다시 쓰면 timeout-or-duplicate 로 거절됩니다. 엔드포인트는 POST https://challenges.cloudflare.com/turnstile/v0/siteverify 입니다. 폼 인코딩과 제이슨 모두 받고, 응답은 항상 제이슨입니다. 필수 칸은 secret 과 response 입니다. remoteip 는 선택입니다. 네트워크가 한 번 끊길 수 있으면 idempotency_key 에 유유아이를 넣어 같은 검증을 다시 부를 수 있습니다.
성공 응답에는 hostname, action, challenge_ts 가 옵니다. 문의 폼이면 action 을 contact 로 맞춰 두고, 서버에서 그 값이 다르면 거절합니다. hostname 이 자기 도메인이 아니면 거절합니다. 엔터프라이즈만 오는 ephemeral_id 는 무료 위젯에 기대지 않습니다.
브라우저에서 사이트베리파이를 부르지 않습니다 | 시크릿이 프론트에 있으면 검증을 우회할 수 있습니다. 문서가 백엔드만 호출하라고 적습니다. 분석 화면에 검증 수가 0이면 이 호출이 빠진 겁니다.
안 보이는 위젯이 항상 편한 건 아닙니다
관리 모드가 공식 권장입니다. 안 보이는 모드는 약관 문장이 필요하고, 실패했을 때 사용자가 다시 누를 칸이 없습니다.
위젯 모드는 세 가지입니다. 관리는 위험도에 따라 자동 통과와 체크 박스를 고릅니다. 비대화형은 스피너만 보이고 클릭을 요구하지 않습니다. 비가시는 위젯 자체도 안 보입니다. 비가시를 켜면 클라우드플레어 턴스타일 개인정보 부속 약관을 자기 개인정보 처리방침에 적어야 한다고 문서가 조건을 겁니다. 12사이트 푸터에 그 문장을 아직 안 넣은 날은, 관리 모드만 켰습니다.
크기가 보통, 가변, 압축입니다. 시에스피가 있으면 script-src 와 frame-src 에 https://challenges.cloudflare.com 을 넣습니다. 논스와 strict-dynamic 도 문서가 지원한다고 적습니다. 헤더 전체는 보안 헤더 글입니다. 여기는 턴스타일 호스트 두 칸만 봅니다.
모드
화면에 남는 것
조건
관리
필요할 때만 체크
기본. 사진 퍼즐 없음
비대화형
스피너
클릭을 원하지 않을 때
비가시
없음
개인정보 부속 약관 문구 필수
플레이라이트 같은 자동 브라우저는 봇으로 잡힙니다. 시험 스위트에는 더미 키를 넣습니다. 항상 실패하는 보이는 키는 2x00000000000000000000AB 입니다. 체크를 강제하는 키는 3x00000000000000000000FF 입니다. 시크릿 3x0000000000000000000000000000000AA 는 이미 쓴 토큰 오류를 돌려줍니다.
관리 모드가 기본이다. 비가시는 개인정보 약관 문장이 있어야 켠다
토큰 5분이 지나면 다시 받아야 하는 이유
토큰은 만들어진 뒤 300초입니다. 긴 문의 글을 쓰다 제출하면 만료됩니다. 페이지를 새로고침하거나 turnstile.reset 으로 다시 받습니다.
문서가 최대 길이를 2,048자로 적습니다. 만료와 재사용은 둘 다 실패입니다. 만료는 사용자가 폼을 오래 열어 둔 경우입니다. 재사용은 서버가 같은 토큰으로 사이트베리파이를 두 번 부른 경우입니다. 웹훅이 두 번 오는 자리와 비슷해 보이지만, 여기는 폼 한 건입니다. 재시도 폭풍은 멱등 글에 둡니다. 사이트베리파이 네트워크만 끊겼다면 같은 idempotency_key 로 검증을 다시 부릅니다. 문의 저장이 끝난 뒤에 같은 토큰으로 한 번 더 부르면 거절이 맞습니다.
페이지가 뜨자마자 위젯이 토큰을 만들면, 첨부 파일을 고르는 동안 5분이 지나기 쉽습니다. 그때는 제출 직전에 실행하는 편이 낫습니다. 공식 클라이언트 렌더 문서의 execute 흐름을 따릅니다. 실패 코드 invalid-input-response 는 토큰이 깨졌거나 만료된 칸입니다. missing-input-secret 은 환경 변수가 빈 칸입니다. invalid-input-secret 은 키를 잘못 붙인 칸입니다.
제출 직전에 위젯을 리셋하고, 시험 키는 환경으로 나눈다
// https://developers.cloudflare.com/turnstile/troubleshooting/testing/
// .env.development
// TURNSTILE_SITEKEY=1x00000000000000000000AA
// TURNSTILE_SECRET_KEY=1x0000000000000000000000000000000AA
function onSubmitClick(form) {
if (window.turnstile) {
window.turnstile.reset()
}
form.requestSubmit()
}
// 더미 토큰은 XXXX.DUMMY.TOKEN.XXXX
// 운영 시크릿은 이 값을 거절한다. 로컬과 운영 키를 섞지 않는다
토큰은 5분이다. 같은 값은 두 번 통과하지 않는다. 오래 쓴 폼은 리셋한다
검증은 서버에서 한 번만 한다
사이트베리파이가 성공한 뒤에만 메일을 보내거나 디비에 넣습니다. 위젯 성공 콜백만 믿고 저장하지 않습니다.
제 순서는 짧습니다. 더미 키로 로컬에서 통과와 실패를 한 번씩 봅니다. 운영 위젯의 호스트 목록에 실제 도메인만 넣습니다. 라우트에서 success, hostname, action 을 대조합니다. 시크릿은 버셀 환경 변수에만 둡니다. 시에스피에 challenges.cloudflare.com 두 칸을 엽니다. 분석에서 검증 수가 0이면 라우트가 빠진 겁니다.
카카오 콜백 주소를 여는 터널과 이 글을 한 설정으로 묶지 않습니다. 턴스타일은 폼 봇 칸입니다. 쿠키 SameSite 는 로그인 세션 칸입니다. 헤더 전체 목록은 보안 헤더 글입니다. 오늘 바꿀 것은 문의 폼 한 곳의 사이트키, 시크릿, 사이트베리파이 세 줄입니다.
참고 자료
턴스타일 시작 - 사이트키와 시크릿, 서버 검증 필수, 토큰 300초. 2026년 5월 5일 갱신
사이트베리파이 - 엔드포인트, 오류 코드, idempotency_key, 호스트와 action 대조. 2026년 5월 5일 갱신
위젯 임베드 - 암시와 명시 렌더, 토큰을 서버로 보내는 흐름. 2026년 6월 17일 갱신
위젯 모드 - 관리, 비대화형, 비가시, 개인정보 부속 약관. 2026년 4월 16일 갱신
시험 키 - 더미 사이트키와 시크릿, localhost, 더미 토큰. 2026년 5월 5일 갱신
시에스피 - script-src 와 frame-src 에 challenges.cloudflare.com. 2026년 5월 5일 갱신