스팸 방지
허니팟, 서명된 타임스탬프, 스팸 점수, 사람 확인, 허용 도메인, 레이트 리밋.
모든 폼은 별도 설정 없이 여러 겹의 보호를 받습니다. 허니팟, 서명된 렌더링 타임스탬프, 스팸 점수, 레이트 리밋이 기본입니다. 여기에 사람 확인을 추가하고 폼을 여러분의 도메인으로 제한할 수 있습니다.
스팸 처리 방식
스팸으로 판정된 POST는 spam 상태로 저장되며 여전히 정상적인 성공 응답을 받으므로, 봇은 아무것도 알 수 없습니다. 스팸은 웹훅과 이메일을 보내지 않고, 월간 사용량에 포함되지 않으며, 30일 후 삭제됩니다. 대시보드나 REST API로 검토하세요. 제출을 스팸이 아님으로 표시하면 그때 웹훅이 전송됩니다.
허니팟
사람은 절대 채우지 않는 숨겨진 입력을 추가하세요. 기본 이름은 _gotcha이며, 폼 설정의 Honeypot field에서 폼마다 바꿀 수 있습니다. 숨기는 방법은 HTML 폼에 있습니다.
<input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />스팸 점수
각 POST는 다음 신호로 점수를 받습니다. 5점 이상이면 스팸으로 저장됩니다.
| 신호 | 대시보드에 표시되는 사유 | 점수 |
|---|---|---|
| 허니팟이 채워짐 | honeypot | +5 |
| 폼이 렌더링된 후 2초 안에 전송됨 | timestamp_fast | +3 |
| 서명된 렌더링 타임스탬프가 위조되었거나 24시간보다 오래됨 | timestamp_invalid | +3 |
| 이름이나 제목 필드에 링크가 있음 | url_in_name | +3 |
| 최근 24시간 안에 같은 필드가 이 폼으로 전송됨 | duplicate | +3 |
| 링크가 모두 3개 이상 | many_urls | +2 |
| 알려진 스팸 문구 (기본 22개 내장) | keyword | +2 |
| 일회용 이메일 도메인의 이메일 주소 | disposable_email | +2 |
- 렌더링 타임스탬프는 서명된
_ikiform_ts를 보내는 SDK나 shadcn/ui 컴포넌트가 폼을 렌더링할 때만 존재합니다. 타임스탬프가 없는 일반 HTML POST는 감점되지 않습니다. - 이름 필드는 이름에
name,firstname,lastname,fullname,surname,subject가 들어 있는 필드이며,company,website,domain같은 단어는 제외됩니다. - 점수와 관계없이 스팸으로 처리되는 사유가 두 가지 있습니다. 허용 도메인에서 오는
origin_mismatch와, Turnstile에 연결할 수 없을 때의captcha_unavailable입니다.
사람 확인
각 폼에는 세 가지 옵션이 있는 Spam check 설정이 있습니다. 기본값은 꺼짐이므로, 폼을 만들어도 기존 fetch와 curl 연동은 계속 작동합니다.
| 옵션 | 하는 일 |
|---|---|
| Off | 챌린지 없음. 허니팟, 타임스탬프, 점수 산정은 계속 실행됩니다. |
| Ikiform (권장) | Ikiform이 ikiform.com에서 실행하는 Cloudflare Turnstile. Cloudflare 계정이나 키가 필요 없습니다. |
| Your own Turnstile keys | 여러분의 Turnstile 위젯과 시크릿 키. 토큰의 호스트 이름을 허용 도메인과 비교합니다. |
Ikiform 확인의 실행 방식
HTML 폼
- 브라우저가 폼을 POST합니다
- 아직 토큰이 없으면 ikiform.com이 짧은 확인 페이지를 보여 줍니다
- Turnstile이 실행되고, 페이지가 같은 필드를 토큰과 함께 다시 POST합니다
- 리디렉션 주소나 감사 페이지로 303
JavaScript
- challenge(formId)가 숨겨진 ikiform.com 프레임을 마운트합니다
- Turnstile이 실행되며, 클릭이 필요할 때만 화면에 나타납니다
- submit(formId, data, { turnstileToken })
- 제출 ID와 함께 201
시크릿 키를 쓰는 서버
- Authorization: Bearer ik_sk_...로 POST
- Ikiform 확인을 건너뜁니다
- 제출 ID와 함께 201
import { challenge, submit } from "@ikiform/sdk";
const form = document.querySelector("form");
form.addEventListener("submit", async (event) => {
event.preventDefault();
const turnstileToken = await challenge("frm_xxxxxxxxxxxxxxxx");
const result = await submit("frm_xxxxxxxxxxxxxxxx", new FormData(form), { turnstileToken });
if (result.ok) form.replaceWith("Thanks, we got it.");
});토큰은 한 번만 쓸 수 있고 300초 동안 유효하므로 제출마다 하나씩 받으세요. 토큰 없는 JSON POST는 challenge_url과 함께 403 challenge_required를 받고, 검증에 실패한 토큰은 403 captcha_failed를 받습니다.
{
"ok": false,
"error": {
"code": "challenge_required",
"message": "Complete the human check, then send the form again.",
"challenge_url": "https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx/challenge"
},
"request_id": "req_01k7v2c9t3e8m5q4x6z0a1b2c3"
}captcha_unavailable 사유와 함께 스팸으로 저장되므로, 잃어버리지 않고 복원할 수 있습니다.Your own Turnstile keys
사이트 키와 시크릿 키를 폼 설정에 붙여 넣으세요. 시크릿은 암호화되어 저장됩니다. 폼에 위젯을 렌더링해 POST에 cf-turnstile-response가 추가되도록 하거나, 토큰을 submit에 turnstileToken으로 전달하세요. 올바른 토큰이 없는 POST는 시크릿 API 키를 쓴 POST를 포함해 모두 403 captcha_failed를 받습니다.
허용 도메인
폼이 있는 도메인을 Allowed domains에 등록하세요. 그러면 POST는 Origin 헤더(출처가 없으면 Referer)를 기준으로 그 도메인이나 하위 도메인에서 와야 합니다. 목록을 비워 두면 어떤 사이트의 POST든 받습니다.
| 허용 도메인 | POST 출처 | 일치 여부 |
|---|---|---|
example.com | https://example.com | 예 |
example.com | https://www.example.com | 예, 하위 도메인 |
example.com | http://localhost:3000 | 아니요, 개발 중에는 localhost를 추가하세요 |
example.com | https://example.co | 아니요 |
app.example.com | https://example.com | 아니요, 상위 도메인은 일치하지 않습니다 |
When a post comes from another site 설정이 불일치 시 동작을 정합니다. Mark as spam(기본값) 또는 403 origin_not_allowed로 Reject합니다. 시크릿 API 키를 쓴 POST는 이 검사를 건너뜁니다. 허용 도메인은 어떤 _redirect 대상을 따를지, Ikiform 확인 프레임을 어디에 임베드할 수 있는지도 결정합니다.
레이트 리밋
각 폼에는 방문자 IP와 폼별로 정확하게 세는 Rate limit 프리셋이 있습니다. Standard는 분당 5건, Relaxed는 분당 20건, Strict는 10분마다 2건을 허용합니다. 전체 규칙은 레이트 리밋에 있습니다.