WebToolsPlanet
Developerguide·7 min read

How to Secure a Public Form Endpoint

Secure a public form endpoint — origin/domain checks, rate limiting, request size limits, and data a form should never collect. Free generator included.

Published 7/26/2026
Updated 7/26/2026

Any endpoint that accepts a POST request from an unauthenticated visitor is, by definition, reachable by anyone — not just visitors filling out your form through your actual page. A form endpoint's security model has to assume every request might come from a script, not a browser, because eventually one will.

Quick Answer

Secure a public form endpoint with, in rough order of impact: origin/domain checks (reject requests not coming from your own site), rate limiting by IP, a request-size limit, honeypot spam protection, and — for anything more sensitive than a contact message — server-side re-validation of every field regardless of what client-side validation already checked. Never collect passwords, payment card numbers, or government ID numbers through a plain contact form. Form Studio generates the client-side honeypot layer automatically; the rest lives on whatever endpoint you connect it to.


Use the Free Tool

Form Studio generates the client-side half of this — honeypot protection and HTML5 field validation — for every export format. The server-side controls below apply to whichever endpoint you point the Delivery tab at, whether that's a third-party provider or your own API route.


Control 1: Origin and Domain Checks

The most direct attack on a public form endpoint is another site's script POSTing to it directly, bypassing your form (and its honeypot) entirely. Checking the request's Origin or Referer header against an allowlist of your own domain(s) blocks the simplest version of this:

export async function POST(request) { const origin = request.headers.get('origin'); const allowedOrigins = ['https://yourdomain.com', 'https://www.yourdomain.com']; if (!origin || !allowedOrigins.includes(origin)) { return Response.json({ error: 'Forbidden' }, { status: 403 }); } // ...proceed with the submission }

This isn't unbeatable — a determined attacker can spoof headers in a server-to-server request — but it stops the overwhelming majority of casual scraping and cross-site abuse with almost no cost.

Control 2: Rate Limiting

Without a limit, a single script can submit thousands of requests per minute. Rate limiting by IP address (and, ideally, by a secondary signal like a session or device fingerprint for attackers rotating IPs) caps the damage a single source can do:

const submissions = new Map(); // in production, use a persistent store like Redis function isRateLimited(ip) { const now = Date.now(); const windowMs = 60_000; const maxRequests = 5; const timestamps = (submissions.get(ip) || []).filter((t) => now - t < windowMs); timestamps.push(now); submissions.set(ip, timestamps); return timestamps.length > maxRequests; }

An in-memory Map like this resets on every server restart and doesn't work across multiple server instances — fine for a low-traffic personal project, but a real deployment should use a shared store (Redis, or your platform's built-in rate-limiting feature if it has one) so the limit holds across restarts and horizontal scaling.

Control 3: Request Size Limits

An unbounded request body lets an attacker send an enormous payload — a multi-megabyte string crammed into a text field — that wastes processing time and storage even if it's not "malicious" in the traditional sense:

export async function POST(request) { const contentLength = Number(request.headers.get('content-length') || 0); if (contentLength > 100_000) { // 100KB return Response.json({ error: 'Payload too large' }, { status: 413 }); } // ... }

A plain contact form's legitimate payload is typically a few hundred bytes to a few kilobytes — a limit well above that still blocks abuse without affecting real users.

Control 4: Honeypot and Field-Count Limits

Covered in more detail in Honeypot vs CAPTCHA for Form SpamForm Studio generates this automatically on the client side. On the server, pair it with a check that the submitted payload doesn't contain unexpected extra fields beyond what your form actually defines — a scripted attack often submits a generic payload shape that doesn't match your specific form.

const expectedFields = ['name', 'email', 'message', 'hp_website']; const unexpectedFields = Object.keys(payload).filter((key) => !expectedFields.includes(key)); if (unexpectedFields.length > 0) { return Response.json({ error: 'Unexpected fields' }, { status: 400 }); }

Control 5: Server-Side Re-Validation

Client-side required, type="email", and pattern attributes are a UX convenience, not a security boundary — anyone can send a request directly to your endpoint that skips the browser and its validation entirely:

function isValidEmail(value) { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value); } if (!payload.name || !payload.email || !isValidEmail(payload.email)) { return Response.json({ error: 'Invalid submission' }, { status: 400 }); }

Re-check every constraint your form's HTML expresses — required fields, length limits, format patterns — on the server, using the same rules, before accepting the submission as legitimate.


What a Form Should Never Collect

Regardless of how well-secured the endpoint is, some data categories don't belong in a plain contact or lead-gen form at all:

  • Passwords or authentication credentials

  • Payment card numbers

  • Government-issued identification numbers

  • Medical records or health information

  • Any other highly sensitive personal data with its own regulatory handling requirements

If your use case genuinely needs one of these (a payment, for instance), route it through a purpose-built, compliant provider (a hosted payment page, for example) rather than passing it through a general contact-form pipeline — those categories carry compliance obligations (PCI-DSS for payment data, HIPAA for health data in relevant contexts) that a generic form endpoint isn't built to satisfy.


Step-by-Step Workflow

Step 1: Add origin/domain checking to your endpoint first — it's the highest-impact, lowest-effort control.

Step 2: Add rate limiting, using a shared store if your endpoint runs on more than one instance.

Step 3: Add a request-size limit sized to your form's realistic maximum legitimate payload.

Step 4: Enable honeypot protection in Form Studio's Behavior tab and add a matching unexpected-field check server-side.

Step 5: Re-implement every client-side validation rule server-side, using the same constraints.

Step 6: Review your field list against the prohibited-data categories above and remove or redirect anything that shouldn't be there.


Common Mistakes

Trusting client-side validation as the only check. Anyone can bypass it entirely with a direct request — it's a UX feature, not a security control.

No rate limiting at all on a public endpoint. Even a simple in-memory limiter is far better than nothing against casual abuse.

Logging full submission payloads indefinitely with no retention policy. Decide how long you actually need submission data and delete it on a schedule — indefinite retention of personal data is both a privacy risk and, in many jurisdictions, a compliance one.

Collecting sensitive data "just in case." Every field you don't need is a field you don't have to secure, retain, or worry about leaking.

Assuming HTTPS alone secures the endpoint. HTTPS protects data in transit from interception — it does nothing to stop an attacker from sending a well-formed, encrypted request directly to your endpoint.


Best Practices

  • Layer controls rather than relying on one. Origin checks, rate limiting, and server-side validation each block a different category of abuse.

  • Fail closed, not open — if a check errors out (a rate-limit store is unreachable, for instance), default to rejecting the request rather than silently letting it through.

  • Return generic error messages ("Invalid submission") rather than detailed validation feedback on a public endpoint, to avoid handing an attacker a map of exactly what your checks look for.

  • Monitor rejected-request volume, not just accepted submissions — a spike in 403s or 429s is an early signal of an attack in progress.

  • Revisit the prohibited-data list whenever you add a new field to the form, not just at initial setup.


  • Form Studio — Build a form with client-side honeypot and validation

  • JSON Formatter — Inspect a payload while testing your endpoint's validation

See also Honeypot vs CAPTCHA for Form Spam and the Developer Tools category for the rest of the form and API utilities.


FAQ

Is origin/domain checking enough on its own?

No — it stops casual cross-site abuse but can be spoofed by a server-to-server request that isn't a real browser. Combine it with rate limiting and server-side validation rather than relying on it alone.

Do I need CAPTCHA to secure an endpoint?

Not necessarily as a first step — origin checks, rate limiting, and honeypot cover most abuse cheaply. CAPTCHA is worth adding if those aren't sufficient, as covered in Honeypot vs CAPTCHA for Form Spam.

How long should I retain submission data?

There's no universal answer — base it on how long you genuinely need the data for follow-up, and document a retention window rather than keeping everything indefinitely by default. Shorter retention reduces both privacy risk and the impact of any future data exposure.

What's the minimum viable security setup for a low-traffic personal site?

Honeypot protection (near-zero cost) plus basic server-side field validation covers the large majority of realistic risk for a low-traffic form. Add rate limiting and origin checks once traffic or attack volume justifies the additional complexity.

Can a third-party form-endpoint provider handle all of this for me?

Many reputable providers implement rate limiting, spam filtering, and basic abuse detection on their end — check a provider's documentation for what's included before assuming you need to build all of this yourself on top of it.


Secure Your Form's Client Side Now

Form Studio generates honeypot protection and full HTML5 validation automatically — free, and it runs entirely in your browser. Pair it with the server-side controls above on whichever endpoint you connect it to.

Khushbu

Khushbu

Full-Stack Developer & Founder

I build tools I wish existed — fast, free, and private. Every tool runs in your browser because I believe your data should stay yours.