Skip to content
Cyber Security21 November 2025 · By the Intense Path Editorial Team

Rate Limiting, Bots and Forms: Stopping Abuse Without Blocking Buyers

Form spam prevention is a layered problem with one rule: the defences that cost a real buyer the most are usually the ones that stop the least. Start at the server and work outwards.

On This Page
Pass It On

Found this useful? Send it to someone who’s building.

Form Spam Prevention Without Blocking Real Buyers | Intense Path

A contact form takes forty submissions overnight, every one of them advertising the same three products in the same broken sentences. Somebody adds a challenge widget on Monday morning. The spam stops immediately, which feels like a win. The enquiries also drop, and nobody notices for six weeks, because there was never a baseline for what a normal Tuesday looked like.

That trade is the entire subject. Form spam prevention is not a question of which widget to install. It is a question of which defences cost a genuine buyer something and which are invisible to them, and the ranking is not the one most teams assume. Our position, stated before the argument: the server is the only layer you cannot skip, and an interactive puzzle is the layer you should reach for last. Everything in between is cheap, quiet and does most of the work.

This matters more on a form that carries revenue than on one that carries a newsletter signup. A blocked spam message costs you nothing. A blocked enquiry costs you the enquiry, and you never find out it happened, which is why abuse controls belong in the same conversation as conversion work rather than in a separate security backlog nobody reads.

Name the traffic before you block it

Almost every bad decision here starts with treating "bots" as one thing. They are not, and a defence tuned for one of them does nothing at all for the others. Five kinds of unwanted traffic hit an ordinary marketing site, and they want different things.

  • Indiscriminate form fillers. Scripts that submit every form they find, to every site they find, for link placement. They rarely execute your JavaScript and they rarely read your markup carefully.
  • Scrapers. They want your prices, your listings or your contact data. They never touch a form at all, so form defences are irrelevant to them.
  • Credential stuffers. They test stolen username and password pairs against your login endpoint. Volume is the signal, and the endpoint is not the contact form.
  • Targeted abuse. Someone who has read your HTML and is submitting deliberately, at human pace. Nothing generic stops this; only server-side rules do.
  • Your own traffic. Uptime monitors, retry logic that retries too eagerly, a preview environment pointing at production. More outages start here than teams admit.

Write down which of those five you actually have before you buy anything. If you are not sure, the honest starting point is measurement rather than defence, and the background reading is why small sites get attacked at all, because most of this traffic is not about you and never was.

The server is the only layer that cannot be skipped

Every defence that runs in the browser is a suggestion. It can be read, understood and stepped around by anyone who opens the network tab, which takes about a minute. That does not make client-side defences worthless. It makes them filters rather than gates, and the distinction decides where you spend effort.

Validate, do not sanitise and hope

Validation means deciding what a valid submission looks like and rejecting everything else: field present or absent, type, length ceiling, allowed characters, allowed values from a fixed list. Sanitising means accepting anything and trying to make it safe afterwards, which is a losing position because it requires you to imagine every attack. The OWASP guidance on injection has said the same thing for years, and it is still the fastest security win available on a form. Treat the form as an instrument rather than a pipe, an argument our parent company makes well in a form is a triage instrument: the fields you ask for decide what you can filter on later.

Single-use tokens and the double submit

Issue a signed, short-lived token when the form is rendered and require it on submission. A script that posts directly to your endpoint without ever loading the page has nothing to send, which removes a whole class of drive-by traffic at no cost to anybody real. Make the token single-use and you also fix the accidental double submit, where an impatient person clicks twice on a slow connection and you file two identical enquiries.

None of this is exotic. It is ordinary application work, and it belongs with whoever owns the application itself rather than being bolted on by a plugin that nobody in the team can explain six months later.

Honeypots and timing checks: quiet, cheap, partly effective

A honeypot is a field a person never sees and a naive script fills in anyway. Anything arriving with that field populated is discarded. It is the best value in this whole article, and it is also the one most often implemented in a way that hurts somebody.

The failure mode is specific. A field hidden with CSS but left in the tab order will be reached by keyboard, announced by a screen reader, and offered to a password manager, which may cheerfully fill it. Now a real person is silently rejected and told nothing. A safe honeypot is removed from the accessibility tree, removed from the tab order, given a name that autofill will not recognise, and marked so browsers do not attempt to complete it. Test it with a screen reader and with autofill switched on, not just with a script. This is exactly the kind of detail our accessibility commitment exists to catch, because nobody reports the bug: they just leave.

Timing checks are the sibling. Record when the form was rendered and reject submissions that arrive implausibly fast, because nobody types a real message in under a second. Keep the floor generous. A person pasting a prepared paragraph into a short form is faster than you think, and a threshold set to catch the last few scripts will start catching people who prepared in advance.

Measure before you defend

You cannot tell whether a defence worked without knowing what normal looked like. For two weeks, count total submissions, count the ones a human would call qualified, and log every rejection with its reason. Without those three numbers, adding a control and watching spam fall tells you nothing about what else fell with it.

Rate limiting: what you count, and where

Rate limiting is the only defence on this list that addresses volume directly, and volume is what turns nuisance traffic into an outage. The mechanics are simple. The judgement is in the keys and the placement.

IP address is a weak key on its own

A single address can be one attacker or an entire office behind one connection, a university campus, or a mobile carrier gateway serving a city. Limit hard on address alone and you will eventually block a whole building because one person there is running a script. Better keys combine signals: the endpoint being called, the account or session where one exists, and the address as a coarse third dimension rather than the only one. Different endpoints deserve different budgets. A login attempt and a newsletter signup should never share a counter.

Decide now whether the limiter fails open or closed

Your limiter depends on a store, and that store will be unavailable at some point. When it is, does every request pass or does every request fail? For a login endpoint, failing closed is defensible. For a contact form, failing closed means a silent outage on the one page that generates revenue. Decide it deliberately, write it down, and make sure the answer differs by endpoint rather than being one global setting nobody revisits.

Two mechanical details are worth getting right. Put the limiter in front of the expensive work, not after it, or you have built a system that carefully rejects requests it has already paid for. And when you reject, return the status code that means too many requests, with a header telling the client how long to wait. Machines that are behaving well will honour it, and the ones that do not have identified themselves for you.

Challenge widgets: the honest accounting

A challenge widget is the defence people reach for first because it is the one they can see working. It is also the only one on this list that charges the visitor directly, and the bill lands unevenly.

An image puzzle asks for vision. An audio alternative asks for hearing and quiet surroundings. A drag interaction asks for fine motor control and a pointing device. A timed challenge penalises anybody who reads slowly, thinks carefully, or is doing this on a train. WCAG treats a challenge as non-text content precisely because it needs alternatives, and providing two inaccessible alternatives is not the same as providing an accessible one. Meanwhile the automation that concerns you most is the automation least troubled by a puzzle, because solving them at volume is a service somebody sells.

There are two further costs teams forget. A widget is third-party JavaScript, which means your content security policy has to open up to accommodate it, and the vendor now observes every visitor who reaches the page. If you are already weighing how much of your interface is loaned to somebody else, building an accessible widget sets out the same tension for chat, and the conclusions carry over almost unchanged.

The concession, and it is a real one: on a login endpoint under sustained credential stuffing, an invisible risk score earns its place. Scoring the request quietly and only challenging the small slice that looks automated is a different proposition from putting a puzzle in front of everyone. What we would not do is make an interactive challenge a permanent fixture on a contact form because of one bad week.

Every defence is a tax. Some of it is paid by the attacker and some by your buyer, and the controls that feel most reassuring usually split it the wrong way.

The comparison that decides the order

Set them side by side on the two axes that matter, what they stop and what they cost somebody real, and the sequence stops being a matter of taste.

DefenceWhat it stopsCost to a real userWhere it fails
Server-side validationMalformed, oversized and malicious payloadsNoneNothing, provided it is the authority rather than a second opinion
Signed single-use form tokenScripts posting straight to the endpointNoneAnything that loads the page first
Honeypot fieldNaive form-filling automationNone, unless autofill or a screen reader reaches itAny script that reads your markup before submitting
Submission timing floorInstant scripted submissionsPrepared visitors pasting a ready answer can trip a strict thresholdA script willing to wait
Per-endpoint rate limitVolume: floods, enumeration, credential stuffingShared offices and carrier gateways share the budgetDistributed sources sending one request each
Invisible risk scoringMost commodity automationOccasional silent false positives nobody can appealDecisions you cannot explain to the person who was blocked
Interactive puzzleSome automation, for a whileHigh: visual, cognitive, motor and timeSolving services, and the people who give up instead

The order we would build it in

Work down this list and stop as soon as the problem stops. Most sites stop at step four, and the ones that do not usually have an endpoint problem rather than a form problem.

  1. Make the server the authority. Every field validated by type, length and allowed values, with rejection on anything unexpected rather than repair.
  2. Issue a signed, short-lived, single-use token with every rendered form and require it on submission.
  3. Add a per-endpoint rate limit in front of the expensive work, with keys that combine endpoint, session and address rather than address alone.
  4. Add a honeypot and a generous timing floor, then test both with a screen reader and with browser autofill enabled.
  5. Log every rejection with its reason, so that a fortnight later you can tell spam from a bug in your own validation.
  6. Only if volume still hurts, add invisible risk scoring, and give somebody the ability to review what it rejected.
  7. Reserve an interactive challenge for the specific endpoint under active attack, and take it off again when the attack ends.

Step seven has a rider that gets ignored: put a removal date on it the day you add it. Temporary controls become permanent through inattention, and two years later nobody remembers why the enquiry form asks people to identify traffic lights. If you are weighing this work against a larger security exercise, fixing the obvious before commissioning a penetration test is the sequencing question underneath it.

When it is already happening

Under an active flood the instinct is to add controls quickly, in whatever order they can be installed. Resist it for twenty minutes and do three things first: confirm which endpoint is being hit, confirm whether the traffic is one source or many, and confirm nothing of yours is contributing. A surprising share of floods turn out to be a monitoring job or a retry loop, which is one of the more common findings when an automated scanner sweeps a site for the first time.

Then decide what you will say. If enquiries were being dropped, somebody was affected, and silence is a choice with consequences, which is the subject of what to say while you still do not know. Afterwards the findings need a shape somebody can act on: a severity, a priority and a recommendation rather than a log excerpt pasted into a channel. That resolution step is what a tool like Prooflin is built for, turning findings into reviewable reports instead of a thread full of screenshots.

The rule we apply

One sentence decides most of these arguments: a defence a legitimate visitor can feel is a defence you have to justify with evidence. Server validation, tokens, honeypots and rate limits are all invisible to somebody filling in a form honestly, so they need no justification at all. A puzzle is felt by everyone, so it needs a reason, a measurement and an end date.

The condition that reverses it: an endpoint where a successful attempt costs you a great deal more than a lost enquiry. Password reset, login, anything that sends mail on a stranger’s behalf, anything that spends money. There, friction is the point, and you should accept the false positives and staff a way for affected people to get past them.

The last piece of advice is unglamorous. Whatever you add, add one thing at a time and leave a fortnight between changes, because two controls shipped together cannot be told apart when the numbers move. If your enquiry volume has dropped since somebody hardened the form and nobody can say which change did it, send us the details and we will start by taking things off rather than putting more on.

Take these with you
Decide which kind of unwanted traffic you actually have before choosing a defence, because a control tuned for form fillers does nothing about scrapers or credential stuffing.
Server-side validation, a signed single-use form token, a honeypot and a per-endpoint rate limit stop most abuse and are completely invisible to a genuine visitor.
A honeypot implemented without care is an accessibility bug that silently rejects real people, so test it with a screen reader and with browser autofill switched on.
Rate limits keyed on IP address alone will eventually block an entire office or carrier gateway; combine endpoint, session and address, and set the fail-open behaviour per endpoint.
Interactive challenges belong on a specific endpoint under active attack, with a removal date attached the day they go on.

Common questions.

Do honeypot fields actually stop spam?

They stop a meaningful share of indiscriminate form-filling scripts for almost no effort, which makes them one of the best value defences available. They do not stop anything that reads your markup before submitting, so they are a filter rather than a gate. Implement one carefully: keep it out of the tab order and the accessibility tree, and give it a name browser autofill will not try to complete.

What is a good rate limit for a contact form?

There is no universal number, because the right limit is derived from your own traffic rather than from a template. Measure normal submission volume per endpoint for a fortnight, then set a ceiling comfortably above the busiest legitimate period you observed. Set separate budgets for login, search and enquiry endpoints, since one shared counter will always be wrong for at least two of them.

Are CAPTCHAs bad for accessibility?

Interactive challenges create real barriers. Image puzzles require vision, audio alternatives require hearing and a quiet environment, and drag interactions require fine motor control and a pointing device. Accessibility guidance treats a challenge as non-text content needing genuine alternatives, and offering two inaccessible options does not satisfy that. Invisible risk scoring, which only challenges suspicious requests, is a considerably better starting point.

Should I block visitors by IP address?

Only as a temporary, narrow measure during an active incident. A single address can represent one attacker or an entire office, campus or mobile carrier gateway, so blocking it can remove a whole group of legitimate visitors at once. Prefer rate limiting keyed on a combination of endpoint, session and address, and set an explicit expiry on any block you do apply.

What HTTP response should a rate-limited request get?

Return the status code meaning too many requests, together with a header stating how long the client should wait before retrying. Well-behaved automation and legitimate integrations will honour it and back off, which reduces your load without any further work. Anything that ignores the instruction and keeps hammering the endpoint has usefully identified itself as a candidate for stricter handling.

How do I tell spam submissions from a broken form?

Log every rejection with the reason attached, not just the fact of it. A sudden rise in rejections all citing the same validation rule is usually your own bug, often introduced by a field change or a new required attribute. A rise spread across several unrelated reasons, arriving from many sources at odd hours, looks like genuine automated abuse.

Facing this in your
own business?

Tell us where you’re headed — we’ll map the shortest honest route.

Start a Project