Reference Cloudflare Worker for accepting public website form submissions without exposing the downstream CRM, ticketing, or automation webhook to the browser.
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
- 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.
- Cloudflare Worker
fetchhandler 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.
Install dependencies:
npm installRun checks:
npm test
npm run build
npm run lintRun locally with Wrangler:
cp wrangler.toml.example wrangler.toml
cp .dev.vars.example .dev.vars
npm run devPublic/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{
"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.
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.