Skip to content

Repository files navigation

Cloudflare Secure Form Worker Reference

Reference Cloudflare Worker for accepting public website form submissions without exposing the downstream CRM, ticketing, or automation webhook to the browser.

Happy Path

Browser form
  -> CORS origin allowlist
  -> request size guard
  -> JSON parse and field normalization
  -> honeypot suppression
  -> server-side Turnstile verification
  -> KV-backed IP rate limit
  -> signed downstream webhook call
  -> generic success/failure response

Why this matters in real SaaS systems

  • Public forms are abuse entry points, so bot checks and rate limits belong at the edge before data reaches internal systems.
  • Downstream webhook URLs are bearer credentials; they should never be shipped in frontend JavaScript.
  • Browser origins, visitor IPs, and webhook providers are separate trust boundaries and should be handled explicitly.

What this repo demonstrates

  • Cloudflare Worker fetch handler for public form submissions.
  • CORS allowlisting with safe preflight behavior.
  • Body-size checks before JSON parsing.
  • Field normalization, required-field validation, max-length limits, consent checks, and stale-form rejection.
  • Honeypot handling that returns success without forwarding spam.
  • Server-side Cloudflare Turnstile verification.
  • Optional KV-backed rate limiting using a hashed IP bucket instead of raw IP keys.
  • Optional HMAC signature for downstream webhook verification.
  • Tests for security decisions and operational failure modes.

Local setup

Install dependencies:

npm install

Run checks:

npm test
npm run build
npm run lint

Run locally with Wrangler:

cp wrangler.toml.example wrangler.toml
cp .dev.vars.example .dev.vars
npm run dev

Configuration

Public/non-secret Worker vars:

ALLOWED_ORIGINS=https://app.example.com,http://localhost:5173
RATE_LIMIT_WINDOW_SECONDS=600
RATE_LIMIT_MAX_SUBMISSIONS=5
MAX_BODY_BYTES=25000
MAX_FORM_AGE_SECONDS=3600

Secrets:

DOWNSTREAM_WEBHOOK_URL=https://hooks.example.com/forms/contact
TURNSTILE_SECRET_KEY=<turnstile-secret>
DOWNSTREAM_SIGNING_SECRET=<shared-webhook-signing-secret>
RATE_LIMIT_KEY_SALT=<salt-for-ip-rate-limit-keys>

Set production secrets with Wrangler:

wrangler secret put DOWNSTREAM_WEBHOOK_URL
wrangler secret put TURNSTILE_SECRET_KEY
wrangler secret put DOWNSTREAM_SIGNING_SECRET
wrangler secret put RATE_LIMIT_KEY_SALT

Request payload

{
  "fullName": "Example Applicant",
  "email": "replace-with-valid-email",
  "phone": "555-0100",
  "message": "I would like to learn more.",
  "consent": true,
  "turnstileToken": "<token-from-widget>",
  "formStartedAt": "2026-08-20T12:00:00.000Z",
  "source": "website-contact-form",
  "companyWebsite": ""
}

companyWebsite is the honeypot field. It should be visually hidden from people and left empty by legitimate clients.

Downstream signature

When DOWNSTREAM_SIGNING_SECRET is configured, the Worker forwards:

x-reference-timestamp: <unix-seconds>
x-reference-signature: sha256=<hex-hmac>

The HMAC message is:

<timestamp>.<json-body>

This lets the receiving system verify that the request came through the Worker and was not replayed outside a short timestamp window.

About

Reference Cloudflare Worker for secure public form submission with Turnstile, CORS allowlisting, KV rate limiting, and signed webhook forwarding.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages