# 폼

> Kanorio 폼을 자체 프론트엔드에 추가하세요. 봇 보호, 파일 업로드, 동의 확인, 연락처 수집 기능을 자동으로 처리합니다.

> Markdown version of https://kanorio.com/ko/developers/guides/forms. Append `.md` to any developer docs URL for this format. Index: https://kanorio.com/llms.txt · MCP: https://app.kanorio.com/api/mcp
>
> Translated from the English original: https://kanorio.com/en/developers/guides/forms.md

관리 화면의 **마케팅 › 온라인 폼**에서 폼을 만들고 프론트엔드에 표시합니다. Kanorio는 봇과 스팸을 확인하고, 제출 내용을 저장하며, 연락처를 생성하거나 업데이트하고, 확인 이메일과 팀 이메일을 발송하며, `customers.form.submitted` 웹훅을 실행합니다.

시작하기 전에 관리 화면에서 폼 ID를 복사하세요. 브라우저가 자체 도메인에서 API를 호출하는 경우, [사이트 설정 › 개발자](https://app.kanorio.com/settings/developer)의 **허용된 오리진**에 해당 도메인을 추가하세요. `localhost`는 항상 허용됩니다.

## 옵션 1: 폼 요소 사용

`<KanorioForm>`은 모든 질문 유형, 파일 업로드, 동의 체크박스를 렌더링하고 봇 보호 기능을 실행합니다. 가장 빠른 방법이며 업그레이드 없이도 새로운 보호 규칙이 적용됩니다.

```tsx
import { KanorioProvider, KanorioForm } from "@kanorio/elements";
import "@kanorio/elements/elements.css";

export function ContactSection() {
  return (
    <KanorioProvider siteUrl="https://s-xxxxxxxxxxxx.kanorio.com">
      <KanorioForm formId="cform12345678901234567890" siteName="Sourdough Studio" />
    </KanorioProvider>
  );
}
```

React를 사용하지 않는 경우 `<kanorio-form site-url="…" form-id="…">`를 사용하세요. [Elements](https://kanorio.com/ko/developers/elements)를 참조하세요.

## 옵션 2: 자체 마크업 사용

HTML을 완전히 제어하고 싶다면 Content Delivery API를 직접 사용하세요.

### 1. 폼 불러오기

`GET /api/v1/forms/{id}`는 질문, 동의 설정, 폼의 응답 수락 여부를 반환합니다. `availability.accepting`이 `false`인 경우 입력란 대신 종료 메시지를 표시하세요.

### 2. 폼 표시 시 봇 체크 가져오기

`GET /api/v1/forms/{id}/challenge`는 해당 제출에 대한 봇 체크를 반환합니다. `provider`는 사이트 소유자가 **사이트 설정 › 봇 보호**에서 설정합니다.

- `kanorio` (기본값): `altcha`에 내장된 작업 증명(proof-of-work) 챌린지입니다. SDK가 자동으로 해결하며(중급 사양 휴대폰에서 약 0.5초 소요), 결과를 `_tk`로 전송합니다.
- `turnstile`, `recaptcha`, `hcaptcha`: 소유자의 자체 키입니다. 해당 제공업체의 위젯을 `siteKey` 및 `action`(Turnstile의 경우 `cData` 포함)으로 렌더링하고, 토큰을 `captchaToken`으로 전달하세요. 서버는 사이트 설정에서 검증할 제공업체를 결정하므로 다른 제공업체의 토큰은 거부됩니다.

제출할 때마다 새로운 챌린지를 가져오세요. 챌린지와 위젯 토큰은 1회용입니다.

### 3. 제출

질문 ID를 키로 하는 답변을 보호 필드와 함께 전송하세요. 허니팟(honeypot) 필드는 비어 있어야 합니다.

```ts
import { forms } from "@kanorio/sdk/forms";

const SITE_URL = "https://s-xxxxxxxxxxxx.kanorio.com";
const FORM_ID = "cform12345678901234567890";
const client = forms.client({ siteUrl: SITE_URL });

// when the form is shown
const renderedAt = Date.now();
const challenge = await client.challenge(FORM_ID);

// when the visitor submits
// built-in: the SDK solves the challenge; third-party: pass the widget token
const protection = await forms.protectionFromChallenge(FORM_ID, challenge, renderedAt, {
  captchaToken: challenge.provider === "kanorio" ? undefined : widgetToken,
});

const result = await client.submit(FORM_ID, {
  answers: { name: "Ada", email: "ada@example.com", message: "Do you ship?" },
  consent: "checked",
  pagePath: window.location.pathname,
  protection,
});

if (!result.ok) {
  // result.error.code, and result.error.issues for INVALID_ANSWERS
}
```

SDK를 사용하지 않는 경우, 다음 본문으로 `POST /api/v1/forms/{id}/submit`을 호출하세요(내장 체크 기준; 타사 제공업체 사용 시 `_tk` 대신 `captchaToken` 전송):

```json
{
  "answers": { "name": "Ada", "email": "ada@example.com" },
  "consent": "checked",
  "pagePath": "/contact",
  "_hp": "",
  "_ts": 1793520000000,
  "_tk": "<solved ALTCHA payload>",
  "_ctx": "cform12345678901234567890"
}
```

제출에 성공하면 `200` 상태 코드와 `{}`를 반환합니다. 그 사이에 폼이 닫혔다면 본문은 `{ "state": "closed" }`, `"not_open"`, 또는 `"full"`이 됩니다.

| 오류 코드 | 조치 방법 |
|---|---|
| `INVALID_ANSWERS` | `details.issues`에 포함된 메시지를 질문 ID별로 표시합니다 |
| `VERIFICATION_EXPIRED` | 새로운 챌린지를 가져와(위젯 초기화) 다시 제출합니다 |
| `DUPLICATE_SUBMISSION` | 양식이 이미 제출되었음을 방문자에게 알립니다 |
| `RATE_LIMITED` | `Retry-After` 시간까지 대기한 후 다시 시도합니다 |

### 파일 업로드

파일 질문의 경우, 먼저 `POST /api/v1/forms/{id}/upload-request`로 업로드 티켓을 요청하세요. 이때 질문 ID, 파일 이름, 유형, 크기를 `forms.uploadProtectionFromChallenge(FORM_ID, challenge, renderedAt, solved)`에서 얻은 보호 필드와 함께 전송합니다. 내장 체크를 사용하는 경우 이는 제출 시 사용하는 해결된 챌린지와 동일하며, 타사 제공업체를 사용하는 경우 챌린지의 `uploadToken`을 사용합니다(위젯 토큰은 1회용이며 제출 시 확인되기 때문). 반환된 `headers`를 사용하여 `uploadUrl`에 `PUT`으로 파일을 업로드한 다음, `uploadId`를 해당 질문의 답변으로 전송하세요. 질문의 제한 용량을 초과하는 파일은 `413 FILE_TOO_LARGE` 오류가 발생합니다.

## 제출 후

- 제출 내용은 관리 화면과 Management API의 `submissions`에서 확인할 수 있습니다.
- 이메일 또는 전화번호 답변을 바탕으로 연락처가 생성되거나 업데이트됩니다.
- 폼 설정에 따라 확인 이메일과 팀 이메일이 발송됩니다.
- 웹훅 구독자에게 `customers.form.submitted`가 전달됩니다.

## 서버에서 전달하기

Next.js 서버 액션과 같이 자체 서버에서 제출할 수 있습니다. 속도 제한은 방문자별로 적용되므로 브라우저에서 직접 제출하는 것이 할당량을 보호하는 데 가장 좋습니다. 서버에서 전달하는 경우 브라우저의 보호 필드를 변경하지 마세요.
