Documentation

Everything you need to get a form live in under five minutes.

Quickstart

  1. 1Sign in with a magic link — no password.
  2. 2Create a project, then a form.
  3. 3Copy the HTML snippet onto your site.
  4. 4Submit it — the lead appears in your inbox.

Endpoint

request
POST https://formheron.com/f/<public_key>
Content-Type: application/x-www-form-urlencoded
# or application/json with Accept: application/json

Bodies are capped at 100 KB. Urlencoded fields use standard URLSearchParams semantics — duplicate names keep the last value. Multipart and file uploads are not supported in v1.

Server-to-server requests

Browsers set Origin automatically, so pasting the snippet into a web page just works. Server-side runtimes (Node, Deno, cron jobs, CI) do not send one. If your form has an allowlist configured, those requests are rejected with origin_not_allowed.

Two ways to make a server-side POST work:

server-side.js
// Option 1 — send an Origin that is on the form's allowlist
await fetch("https://formheron.com/f/YOUR_KEY", {
  method: "POST",
  headers: {
    "content-type": "application/json",
    "origin": "https://yoursite.com", // must match the allowlist
  },
  body: JSON.stringify({ email: "[email protected]", message: "Hello" }),
});

// Option 2 — leave the form's allowlist empty (open form).
// Then Origin is not required at all. Rely on the honeypot,
// rate limits and Turnstile for spam defence instead.

The allowlist is an abuse-reduction control, not authentication — Origin is client-controlled and can be forged. Real bot defence is the honeypot, rate limits and Turnstile.

Honeypot

Always include a _hp field positioned off-screen. Do not use type="hidden" — password managers fill those and flag real people as bots.

honeypot.html
<input type="text" name="_hp" tabindex="-1" autocomplete="off"
       aria-hidden="true" style="position:absolute;left:-9999px" />

Turnstile — bring your own keys

Create a free Cloudflare Turnstile widget for your own domain, then paste the site key and secret into your form settings. FormHeron never uses a shared sitekey for customer forms, so your captcha analytics and hostname list stay in your Cloudflare account.

Error codes

Branch on the stable code field, not the HTTP status alone.

quota_exceeded402Monthly limit reached — upgrade or wait for the next period.
rate_limited429Too many requests. Retry after the window closes.
origin_not_allowed403Origin missing or not on the form's allowlist. Server-side requests send no Origin — see Server-to-server below.
turnstile_failed403Captcha verification failed.
turnstile_misconfigured403The form's Turnstile secret is invalid.
too_large413Body over 100 KB, or too many fields.
unknown_key404No active form matches that public key.

Webhooks

Available on Indie and Pro. Every payload is signed as HMAC-SHA256(secret, timestamp + "." + raw_body) and sent with X-FormHeron-Signature and X-FormHeron-Timestamp.

verify.js
const crypto = require("crypto");

function verify(secret, rawBody, signature, timestamp) {
  // Reject replays older than five minutes
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false;

  const expected = crypto
    .createHmac("sha256", secret)
    .update(timestamp + "." + rawBody)
    .digest("hex");

  const a = Buffer.from(expected);
  const b = Buffer.from(signature);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Frameworks

Next.js · Astro · plain HTML

Point the form action at your endpoint URL. Nothing else to configure.

Webflow · Framer

Set the form action to your FormHeron URL with method POST, and add the _hp field as a custom code embed.

Ready to create your first form?

Start free