# 내 뉴스레터 폼의 가입을 beehiiv로 보내기

> 내 가입 폼을 beehiiv에 연결합니다. 릴레이가 더블 옵트인을 켜고, 위치를 UTM source로, 페이지를 referrer로 해서 각 구독을 만듭니다.

2026-10-25 게시 · https://ikiform.com/ko/guides/newsletter-signup/beehiiv

내 폼을 Ikiform으로 POST하고, beehiiv의 구독 생성 엔드포인트를 호출하는 릴레이로 웹훅을 보내세요. beehiiv가 확인 이메일을 보내도록 `double_opt_override: "on"`을 보내고, 폼의 위치는 `utm_source`로, 페이지는 `referring_site`로 넘기고, 가입이 수신 거부를 조용히 되돌리지 않도록 `reactivate_existing`은 false로 두세요. 가입마다 API 호출 한 번이며, 키는 릴레이에 둡니다.

## beehiiv에서 다른 점

beehiiv의 API는 퍼블리케이션을 중심으로 구성됩니다. 구독은 한 퍼블리케이션을 구독한 이메일 주소이며, `/v2/publications/{publicationId}/subscriptions`로 `POST` 한 번이면 만들어집니다. 가입에 관한 모든 것이 그 요청 하나에 담깁니다. 확인할지, 환영 이메일을 보낼지, 독자가 어디서 왔는지, 커스텀 필드입니다. [Kit](https://ikiform.com/ko/guides/newsletter-signup/kit.md)처럼 별도의 태그나 폼 단계가 없습니다.

beehiiv는 유입도 UTM 용어로 보고합니다. 구독 레코드에는 `utm_source`, `utm_medium`, `utm_campaign`, `referring_site`가 있습니다. 폼의 필드를 여기에 매핑하면 beehiiv의 각 구독자에게 어느 위치와 어느 글에서 왔는지 표시됩니다.

다이어그램: 릴레이가 Ikiform 제출을 beehiiv 구독으로 매핑하는 방식

1. fields.email은 email이 됩니다.
2. fields.source는 utm_source가 됩니다.
3. meta.referrer는 referring_site가 됩니다.
4. fields.first_name은 custom_fields: First Name이 됩니다.
5. double_opt_override: "on"은 릴레이가 추가합니다.
6. reactivate_existing: false는 릴레이가 추가합니다.

처음 네 개는 Ikiform 이벤트에서 옵니다. 마지막 두 개는 릴레이가 모든 가입에 대해 정하는 고정 값입니다.

## beehiiv 설정하기

1. beehiiv에서 **Settings**를 열고, **Workspace Settings** 아래의 **API**에서 **Create New API Key**를 고르세요. 바로 복사하세요. beehiiv는 다시 보여 주지 않습니다.
2. 유료 플랜이라면 키를 폼이 연결되는 퍼블리케이션 하나로 제한하세요. 퍼블리케이션 범위의 키는 다른 퍼블리케이션에 대해 `404`를 받으므로, 유출되었을 때 피해가 제한됩니다.
3. 퍼블리케이션 ID를 찾으세요. beehiiv API가 경로에 쓰는 접두사가 붙은 ID입니다. API의 퍼블리케이션 목록 엔드포인트가 반환합니다.
4. 이름을 저장하고 싶다면 `First Name`이라는 커스텀 필드를 만드세요. beehiiv는 아직 존재하지 않는 커스텀 필드를 버립니다.
5. 퍼블리케이션 설정에서 더블 옵트인 이메일과 환영 이메일을 설정하세요. 릴레이는 요청마다 이를 켜고, 내용은 beehiiv에 있습니다.

> beehiiv 요금 페이지에는 모든 플랜에 API 접근이 있고, 저렴한 플랜일수록 API 레이트 리밋이 낮다고 나와 있습니다. API로 이메일을 보내는 Send API는 상위 플랜으로 제한되지만, 이 릴레이는 구독만 만들므로 필요 없습니다.

## 릴레이

이것은 Netlify Function이며, 이미 Netlify에 있는 사이트에 잘 맞습니다. 별도 서비스 없이 정적 사이트 옆, 내 도메인에서 실행됩니다. 타입을 위해 `@netlify/functions`를 설치하고, 사이트의 환경 설정에 변수 세 개를 설정하세요.

`netlify/functions/beehiiv.mts`:

```ts
import type { Config } from "@netlify/functions";
import { verifyWebhook, WebhookVerificationError } from "@ikiform/sdk/webhooks";

export default async (request: Request) => {
  const payload = await request.text();
  let event;
  try {
    event = await verifyWebhook({
      payload,
      headers: request.headers,
      secret: Netlify.env.get("IKIFORM_WEBHOOK_SECRET")!,
    });
  } catch (error) {
    if (error instanceof WebhookVerificationError) return new Response(error.reason, { status: 400 });
    throw error;
  }
  if (event.type !== "submission.created") return new Response(null, { status: 204 });

  const { email, first_name, consent, source } = event.data.fields;
  if (consent !== true || typeof email !== "string") return new Response("Skipped", { status: 200 });

  const publication = Netlify.env.get("BEEHIIV_PUBLICATION_ID");
  const answer = await fetch(`https://api.beehiiv.com/v2/publications/${publication}/subscriptions`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${Netlify.env.get("BEEHIIV_API_KEY")}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      email,
      double_opt_override: "on",
      send_welcome_email: true,
      reactivate_existing: false,
      utm_source: typeof source === "string" ? source : "website",
      utm_medium: "signup_form",
      referring_site: event.data.meta.referrer ?? undefined,
      custom_fields: typeof first_name === "string" && first_name ? [{ name: "First Name", value: first_name }] : [],
    }),
  });

  if (answer.status === 429) {
    const reset = Number(answer.headers.get("ratelimit-reset"));
    const wait = reset ? Math.max(1, reset - Math.floor(Date.now() / 1000)) : 60;
    return new Response("beehiiv rate limit", { status: 503, headers: { "retry-after": String(wait) } });
  }
  if (!answer.ok) return new Response(`beehiiv answered ${answer.status}: ${await answer.text()}`, { status: 502 });
  return new Response(null, { status: 204 });
};

export const config: Config = {
  path: "/hooks/beehiiv",
};
```

- `double_opt_override: "on"`은 퍼블리케이션 기본값과 관계없이 확인 이메일을 보냅니다. 퍼블리케이션 설정을 따르려면 `"not_set"`을 쓰고, 공개 폼에는 절대 `"off"`를 쓰지 마세요.
- API에서 `send_welcome_email`의 기본값은 `false`입니다. 임베드 가입자가 받는 환영 이메일을 API 가입자도 받게 하려면 `true`로 설정하세요.
- `reactivate_existing: false`도 기본값입니다. 나중에 누가 생각 없이 바꾸지 않도록 명시하세요. 수신 거부한 사람을 다시 활성화합니다.
- beehiiv는 구독과 함께 `200`으로 응답하며, `status`는 beehiiv가 주소를 확인하는 동안 `validating`으로 시작해 독자가 확인할 때까지 `pending`입니다.
- `429`가 오면 릴레이가 Unix 시간인 beehiiv의 `RateLimit-Reset`을 읽어 Ikiform용 `Retry-After` 지연으로 바꿉니다.

함수의 URL은 `https://your-site.com/hooks/beehiiv`입니다. Ikiform의 **Webhooks**에 추가하고 `whsec_` 시크릿을 `IKIFORM_WEBHOOK_SECRET`에 복사하세요. URL 규칙은 [웹훅 문서](https://ikiform.com/ko/docs/webhooks.md)에 있고, [자체 서버](https://ikiform.com/blog/form-submissions-to-your-own-server.md)에서 이미 처리한 전달을 건너뛰는 방법을 보여 줍니다.

## UTM 값 정하기

beehiiv의 UTM 필드는 자유 텍스트이므로 값을 한 번 정하고 일관되게 유지하세요. 그러지 않으면 `footer`와 `Footer`처럼 거의 같은 기록으로 갈라집니다. 폼의 hidden `source` 입력이 자연스러운 `utm_source`입니다. `utm_medium`은 고정해 두면 내 폼에서 온 가입을 beehiiv 호스팅 페이지나 가져오기와 구분할 수 있습니다.

| 위치 | `source` 입력 | `utm_source` | `utm_medium` |
| --- | --- | --- | --- |
| 사이트 푸터 | `footer` | `footer` | `signup_form` |
| 글의 끝 | `post` | `post` | `signup_form` |
| 뉴스레터 페이지 | `newsletter-page` | `newsletter-page` | `signup_form` |

폼이 있는 페이지로 링크하는 캠페인을 운영한다면, 릴레이에서 페이지 URL로부터 캠페인을 읽으세요. `meta.referrer`에는 브라우저가 보낸 경우 쿼리 문자열까지 포함한 페이지의 전체 URL이 담기므로, 릴레이가 그 안의 `utm_campaign` 파라미터를 요청에 복사할 수 있습니다.

## 플랜별 레이트 리밋

beehiiv는 API 키별로 고정된 60초 구간에서 한도를 측정하며, 한도는 플랜에 따라 다릅니다. 가입마다 요청 하나입니다.

| beehiiv 플랜 | 분당 요청 수 |
| --- | --- |
| Free | 30 |
| Lite | 180 |
| Pro | 500 |
| Enterprise | 800, 요청 시 상향 가능 |

분당 30건이면 대부분의 푸터 폼에 충분하지만, 무료 퍼블리케이션의 출시 글이 이를 넘을 수 있습니다. 릴레이의 `503` 덕분에 Ikiform은 가입을 버리지 않고 기다렸다가 재시도합니다. 플랜 이름과 한도는 바뀌므로, 내 플랜이 목록에 없다면 beehiiv의 레이트 리밋 페이지를 확인하세요.

## 주의할 점

- **커스텀 필드 이름은 정확해야 합니다.** beehiiv는 이름으로 커스텀 필드를 맞추고, 모르는 필드는 오류가 아니라 응답의 경고와 함께 버립니다. 오타가 있으면 이름이 조용히 사라집니다.
- **`referring_site`는 비어 있을 수 있습니다.** 브라우저가 referrer를 보내지 않으면 `meta.referrer`는 `null`이고, 릴레이는 그 필드를 뺍니다.
- **기존 구독자.** beehiiv 문서에 따르면 이 호출은 구독을 만들거나 업데이트하지만, 활성 구독자에게 무엇이 바뀌는지는 나와 있지 않습니다. 폼을 모든 페이지에 두기 전에 확인된 내 주소로 테스트하세요.
- **키는 기본적으로 워크스페이스 전체에 적용됩니다.** 퍼블리케이션 제한이 없는 키는 워크스페이스의 모든 퍼블리케이션에 접근할 수 있습니다. 범위를 제한하세요.
- **유료 등급은 beehiiv에 남습니다.** 가입이 유료 구독 없이 이루어지도록 `tier`는 빼세요. 업그레이드는 beehiiv 자체 결제에서 이루어집니다.

## 작동 확인하기

1. Ikiform의 엔드포인트에서 **Send test**를 누르세요. 전달에 `204`가 표시되고 beehiiv에는 요청이 가지 않습니다.
2. 내 주소로 페이지에서 구독하세요. 전달에 `204`가 표시됩니다.
3. beehiiv에서 구독자가 pending으로 나타나며, 위치는 UTM source로, 글은 referring site로 표시됩니다.
4. 더블 옵트인 이메일이 도착합니다. 확인하면 환영 이메일이 이어집니다.
5. beehiiv에서 그 주소를 수신 거부하고 다시 가입하세요. `reactivate_existing`이 false이므로 구독자는 비활성 상태로 남습니다.

## 질문

### beehiiv 임베드 대신 내 폼을 쓸 수 있나요?

네. 폼을 Ikiform으로 POST하고, 릴레이가 가입마다 beehiiv의 구독 생성 엔드포인트를 호출하게 하세요. 폼은 내 디자인을 유지하고, 확인, 환영, 이메일 발송은 여전히 beehiiv가 합니다.

### beehiiv API는 모든 beehiiv 플랜에서 동작하나요?

beehiiv 요금 페이지에는 모든 플랜에 API 접근이 있다고 나와 있고, 레이트 리밋 페이지는 가장 낮은 플랜에 분당 30개 요청을 줍니다. Send API는 상위 플랜으로 제한되지만, 구독 생성에는 쓰이지 않습니다.

### API 가입에 대해 beehiiv가 더블 옵트인 이메일을 보내게 하려면 어떻게 하나요?

구독 생성 요청에서 double_opt_override를 "on"으로 보내거나, 퍼블리케이션 자체 설정을 쓰려면 "not_set"을 보내세요. "off"는 이메일 없이 구독자를 바로 활성화합니다.

### 코드 없이 beehiiv를 연결할 수 있나요?

네. beehiiv는 Zapier, Make와 함께 동작하며, 이 도구들이 레이트 리밋을 대신 지켜 줍니다. Ikiform 웹훅을 자동화로 보내고 구독자를 추가하는 beehiiv 액션을 쓰세요.

## 가이드 더 보기

- [뉴스레터 가입 폼](https://ikiform.com/ko/guides/newsletter-signup.md): 직접 작성한 마크업으로 뉴스레터 가입 폼을 만들고, 동의를 기록하고, 봇 가입을 막고, 각 구독자를 Kit, MailerLite, beehiiv 등 어떤 도구로든 넘깁니다.
- [더블 옵트인](https://ikiform.com/ko/guides/newsletter-signup/double-opt-in.md): Ikiform은 확인 이메일을 보내지 않으므로 더블 옵트인은 이메일 도구에서 실행됩니다. 릴레이가 각 가입을 미확인으로 추가하고 도구가 링크를 보냅니다.
- [정적 사이트](https://ikiform.com/ko/guides/newsletter-signup/static-site.md): Hugo, Jekyll, Eleventy, Astro 정적 사이트에 뉴스레터 가입 폼을 추가하고, 이메일 도구의 API 키는 페이지에서 빼고, 확인은 도구에서 합니다.
- [Kit (구 ConvertKit)](https://ikiform.com/ko/guides/newsletter-signup/kit.md): 구독자를 만들고, 인센티브 이메일을 위해 Kit 폼에 추가하고, 가입 위치를 태그하는 릴레이로 내 뉴스레터 폼을 Kit에 연결합니다.
- [MailerLite](https://ikiform.com/ko/guides/newsletter-signup/mailerlite.md): 내 가입 폼을 MailerLite에 연결합니다. 릴레이가 새 독자를 더블 옵트인을 위해 미확인으로 추가하고, 구독자에게 두 번 확인을 요청하지 않습니다.
- [Next.js 뉴스레터 폼](https://ikiform.com/ko/guides/newsletter-signup/nextjs.md): Next.js 푸터 뉴스레터 폼입니다. 동의 확인과 Turnstile이 있는 서버 액션이 Ikiform으로 POST하고, 라우트 핸들러가 독자를 Kit에 추가합니다.
