Documentation
Spam protection
Layered, scored, and silent to whatever triggers it. Every submission runs through the same pipeline before it reaches storage or a destination.
How scoring works
Every layer below produces a partial score between 0 and 1; they add up. Once the total passes your form's threshold (default 0.8, configurable per form), the submission is quarantined - stored and visible in your inbox, but never delivered to a destination. A few signals (honeypot filled, disposable email when that option is set to hard-block, a blocked domain match) reject outright at score 1.0 regardless of threshold.
Either way, the sender always sees a normal success response. Returning an error to a rejected submission is how a bot learns what got it caught - silence is the point.
Signals
- Honeypot fieldA field name (_gotcha by default, or your own choice) that a real visitor never sees or fills, hidden with CSS. A bot filling every field trips it instantly. Rejects outright.
- Time trapSet _start_time to a timestamp when the form renders. A submission completed in under ~2 seconds (configurable) is treated as scripted.
- Disposable emailChecks the submitted email's domain against a list of throwaway-email providers. Can hard-block or just add to the score.
- Blocked domains / keywordsPer-form lists you maintain - block specific sending domains outright, or flag content containing chosen keywords.
- Link floodMany URLs packed into one field is a strong spam signal on its own.
- Origin mismatchIf you've set an allowed-origins list for the form, a submission whose Origin header doesn't match adds to the score rather than being silently accepted.
- Empty or tiny contentA submission with almost nothing in it (common with scripted probing) scores slightly higher.
- Sender reputationSubmissions are tracked by sender identity (email/domain, never IP) across your workspace. A sender previously confirmed as spam by the AI classifier scores worse next time, for free, without another model call.
- AI classifierA model call as a second opinion, only when the deterministic score is ambiguous enough that it could change the outcome - not run on every submission. Available on plans with AI spam detection.
- CaptchaTurnstile, hCaptcha or reCAPTCHA, verified server-side against the token in _captcha (or the provider's own field name).
Setting up a honeypot
Add a field named _gotcha (or your form's configured honeypot field name) and hide it with CSS - not type="hidden", which some bots skip.
<input type="text" name="_gotcha" style="position:absolute;left:-9999px" tabindex="-1" autocomplete="off" />
Setting up the time trap
Stamp a hidden field with the current time when the form renders, so the server can measure how long it took to submit:
<input type="hidden" name="_start_time" id="start-time" />
<script>
document.getElementById("start-time").value = Date.now();
</script>Configuring per form
Every signal is a toggle or a threshold in Form → Settings → Spam, and the same shape is writable from the write API:
curl -X PATCH https://api.webforms.to/v1/forms/frm_xxx \
-H "Authorization: Bearer wf_live_your_key" \
-H "Content-Type: application/json" \
-d '{
"spamConfig": {
"honeypot": true,
"timeTrapMs": 2000,
"captcha": "turnstile",
"blockDisposableEmail": true,
"blockedKeywords": ["viagra", "crypto airdrop"],
"allowedOrigins": ["example.com"]
}
}'Reviewing quarantine
Quarantined submissions live alongside genuine ones in the dashboard, filterable by status, so nothing caught by a false-positive is actually lost - you can review and release it. The Submissions API excludes them by default; pass status=quarantined to include them.
Get your endpoint
Free for 300 submissions a month. No card required.
Start free