# Send your own newsletter form's signups to beehiiv

> Connect your own signup form to beehiiv: a relay creates each subscription with double opt-in on, the placement as UTM source and the page as referrer.

Published 2026-10-25 · https://ikiform.com/guides/newsletter-signup/beehiiv

Post your own form to Ikiform and point the webhook at a relay that calls beehiiv's create subscription endpoint. Send `double_opt_override: "on"` so beehiiv emails the confirmation, pass the form's placement as `utm_source` and the page as `referring_site`, and keep `reactivate_existing` false so a signup never quietly undoes an unsubscribe. One API call per signup, with the key kept in the relay.

## What's different with beehiiv

beehiiv's API is organised around publications. A subscription is an email address subscribed to one publication, and you create it with one `POST` to `/v2/publications/{publicationId}/subscriptions`. Everything about the signup travels in that one request: whether to confirm, whether to send the welcome email, where the reader came from, and custom fields. There's no separate tag or form step as in [Kit](https://ikiform.com/guides/newsletter-signup/kit.md).

beehiiv also reports acquisition in UTM terms. Its subscription records carry `utm_source`, `utm_medium`, `utm_campaign` and `referring_site`. Map your form's fields onto those, and each subscriber in beehiiv shows which placement and which article brought them in:

Diagram: How the relay maps an Ikiform submission to beehiiv subscription

1. fields.email becomes email.
2. fields.source becomes utm_source.
3. meta.referrer becomes referring_site.
4. fields.first_name becomes custom_fields: First Name.
5. double_opt_override: "on" is added by the relay.
6. reactivate_existing: false is added by the relay.

The first four come from the Ikiform event. The last two are fixed choices the relay makes for every signup.

## Set up beehiiv

1. In beehiiv, open **Settings**, then **API** under **Workspace Settings**, and choose **Create New API Key**. Copy it right away; beehiiv won't show it again.
2. On a paid plan, restrict the key to the one publication the form feeds. A publication-scoped key gets `404` for any other publication, which limits the damage if it leaks.
3. Find the publication's id, the prefixed id beehiiv's API uses in the path. The API's list publications endpoint returns it.
4. Create a custom field named `First Name` if you want to store first names. beehiiv discards custom fields that don't exist yet.
5. Set up the double opt-in and welcome emails in the publication's settings. The relay turns them on per request; their content lives in beehiiv.

> beehiiv's pricing page lists API access on every plan, with lower API rate limits on cheaper plans. Its Send API, for sending email through the API, is limited to higher plans, but this relay only creates subscriptions and doesn't need it.

## The relay

This is a Netlify Function, which suits a site already on Netlify: it runs on your own domain, next to the static site, with no separate service. Install `@netlify/functions` for the types, and set the three variables in the site's environment settings.

`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"` sends the confirmation email whatever the publication's default is. Use `"not_set"` to follow the publication setting instead, and never `"off"` for a public form.
- `send_welcome_email` defaults to `false` in the API. Set it to `true` if you want API signups to get the welcome email that embed signups get.
- `reactivate_existing: false` is the default too. Spell it out so nobody flips it later without thinking; it reactivates people who unsubscribed.
- beehiiv answers `200` with the subscription, and its `status` starts as `validating` while beehiiv checks the address, then `pending` until the reader confirms.
- On `429`, the relay reads beehiiv's `RateLimit-Reset`, a Unix time, and turns it into a `Retry-After` delay for Ikiform.

The function's URL is `https://your-site.com/hooks/beehiiv`. Add it under **Webhooks** in Ikiform and copy the `whsec_` secret into `IKIFORM_WEBHOOK_SECRET`. The [webhooks docs](https://ikiform.com/docs/webhooks.md) list the URL rules, and [your own server](https://ikiform.com/blog/form-submissions-to-your-own-server.md) shows how to skip a delivery you've already handled.

## Choosing UTM values

beehiiv's UTM fields are free text, so decide the values once and keep them consistent, or the records split into near-duplicates such as `footer` and `Footer`. The form's hidden `source` input is the natural `utm_source`. Keep `utm_medium` fixed, so every signup from your own forms can be told apart from beehiiv's hosted pages and from imports:

| Placement | `source` input | `utm_source` | `utm_medium` |
| --- | --- | --- | --- |
| Site footer | `footer` | `footer` | `signup_form` |
| End of a post | `post` | `post` | `signup_form` |
| Newsletter page | `newsletter-page` | `newsletter-page` | `signup_form` |

If you run a campaign that links to a page with the form, read the campaign from the page URL in the relay. `meta.referrer` holds the full URL of the page, query string included when the browser sends it, so the relay can copy a `utm_campaign` parameter from it into the request.

## Rate limits by plan

beehiiv measures its limit per API key in fixed 60-second windows, and the limit depends on the plan. Each signup is one request:

| beehiiv plan | Requests per minute |
| --- | --- |
| Free | 30 |
| Lite | 180 |
| Pro | 500 |
| Enterprise | 800, higher on request |

Thirty a minute is enough for most footer forms, but a launch post on a free publication can pass it. The relay's `503` makes Ikiform wait and retry rather than drop the signup. Plan names and limits change, so check beehiiv's rate limiting page if yours isn't listed.

## Gotchas

- **Custom field names are exact.** beehiiv matches custom fields by name and drops unknown ones with a warning in the response, not an error. A typo means first names quietly go missing.
- **`referring_site` can be empty.** `meta.referrer` is `null` when the browser didn't send a referrer, and the relay leaves the field out.
- **Existing subscribers.** beehiiv's docs say the call creates or updates the subscription, but not what changes for an active subscriber. Test with your own confirmed address before you put the form on every page.
- **Keys are workspace-wide by default.** A key without a publication restriction can reach every publication in the workspace. Scope it.
- **Paid tiers stay in beehiiv.** Leave `tier` out so signups join without a paid subscription. Upgrades happen in beehiiv's own checkout.

## Check it works

1. Choose **Send test** on the endpoint in Ikiform. The delivery shows `204` and beehiiv gets no request.
2. Subscribe from your page with an address you own. The delivery shows `204`.
3. In beehiiv, the subscriber appears as pending, with your placement as UTM source and the article as referring site.
4. The double opt-in email arrives. Confirm it, and the welcome email follows.
5. Unsubscribe that address in beehiiv and sign up again. The subscriber stays inactive, because `reactivate_existing` is false.

## Questions

### Can I use my own form with beehiiv instead of its embed?

Yes. Post your form to Ikiform and let a relay call beehiiv's create subscription endpoint for each signup. Your form keeps your design, and beehiiv still confirms, welcomes and emails the reader.

### Does beehiiv's API work on every beehiiv plan?

beehiiv's pricing page lists API access on every plan, and its rate limiting page gives its lowest plan 30 requests a minute. The Send API is limited to higher plans, but creating subscriptions doesn't use it.

### How do I make beehiiv send a double opt-in email for API signups?

Send double_opt_override set to "on" in the create subscription request, or "not_set" to use the publication's own setting. "off" makes the subscriber active immediately with no email.

### Can I connect beehiiv without code?

Yes. beehiiv works with Zapier and Make, which follow its rate limits for you. Point the Ikiform webhook at the automation and use its beehiiv action to add a subscriber.

## More guides

- [Newsletter signup form](https://ikiform.com/guides/newsletter-signup.md): Build a newsletter signup form in your own markup, record consent, block bot signups and pass each subscriber to Kit, MailerLite, beehiiv or any tool.
- [Double opt-in](https://ikiform.com/guides/newsletter-signup/double-opt-in.md): Ikiform doesn't send confirmation emails, so double opt-in runs in your email tool: a relay adds each signup as unconfirmed and the tool sends the link.
- [Static site](https://ikiform.com/guides/newsletter-signup/static-site.md): Add a newsletter signup form to a Hugo, Jekyll, Eleventy or Astro static site, keep the email tool's API key out of the page and confirm in the tool.
- [Kit (ConvertKit)](https://ikiform.com/guides/newsletter-signup/kit.md): Connect your own newsletter form to Kit with a relay that creates the subscriber, adds them to a Kit form for the incentive email, and tags the placement.
- [MailerLite](https://ikiform.com/guides/newsletter-signup/mailerlite.md): Connect your own signup form to MailerLite: a relay adds new readers as unconfirmed for double opt-in and never asks a subscriber to confirm twice.
- [Next.js newsletter form](https://ikiform.com/guides/newsletter-signup/nextjs.md): A Next.js footer newsletter form: a server action with a consent check and Turnstile, posting to Ikiform, plus a route handler that adds readers to Kit.
