BETA
Skip to content

Spam Protection

Krafter Forms includes built-in spam protection through honeypot fields and per-IP rate limiting. No third-party services or CAPTCHAs required.

Honeypot Fields

A honeypot is a hidden form field that is invisible to real users but gets filled in by automated bots. When a submission includes a non-empty honeypot value, it is flagged as spam.

How It Works

  1. Add a hidden field named _honeypot or _gotcha to your form.
  2. Hide it from real users using CSS.
  3. Bots that crawl and fill in all fields will populate the honeypot.
  4. Krafter detects the filled honeypot and flags the submission as spam.

HTML Example

html
<form action="https://app.krafter.dev/f/contact" method="POST">
  <!-- Honeypot field: hidden from humans, visible to bots -->
  <div style="position: absolute; left: -9999px;">
    <input type="text" name="_honeypot" tabindex="-1" autocomplete="off">
  </div>

  <input type="text" name="name" required>
  <input type="email" name="email" required>
  <textarea name="message" required></textarea>
  <button type="submit">Send</button>
</form>

WARNING

Do not use display: none or visibility: hidden to hide the honeypot field. Some bots detect these CSS properties and skip hidden fields. Use off-screen positioning (left: -9999px) instead.

What Happens to Spam Submissions

When a honeypot is triggered:

  • The submission is still stored in the database.
  • The is_spam flag is set to true.
  • The spam_score field is populated.
  • Spam submissions are hidden by default in the dashboard and API responses.
  • Email notifications and webhooks are not triggered for spam submissions.

The server still returns a 201 response so bots do not know they were detected:

json
{
  "ok": true,
  "id": "a1b2c3d4-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}

Rate Limiting

Two independent rate-limit layers protect every public form submission endpoint. Both are tracked in-memory using ETS on each Krafter node.

LayerScopeDefaultConfigurable429 body
Platform pipelineAll /f/* traffic per IP, per node200 requests / 60sNo (platform-wide ceiling on every /f/* route){"error": "Rate limit exceeded", "retry_after": <seconds>} plus X-RateLimit-* and Retry-After headers
Per-form RateLimiterPer (form, IP), per node10 requests / 60sYes — settings.rate_limit on the form{"error": "rate_limited"}

The platform pipeline cap is applied first by KrafterWeb.Plugs.RateLimit in the :public_rate_limit pipeline, so raising settings.rate_limit above 200/min has no effect — the pipeline cap kicks in earlier on busy IPs.

The per-form rate-limit counters live in an in-memory ETS table on each Krafter node and are cleaned up every 5 minutes by the Krafter.Forms.RateLimiter GenServer. Counts are not shared across nodes — see the multi-node note in the Forms audit (Q8) before sizing limits.

The two layers are distinguishable by their JSON body shape and by whether X-RateLimit-* headers are present (they are only set by the platform pipeline, not by the per-form limiter).

Configuring Rate Limits

You can customize the rate limit per form via the form settings. Set settings.rate_limit when creating or updating a form:

bash
curl -X PATCH https://app.krafter.dev/api/v1/forms/FORM_ID \
  -H "Authorization: Bearer kr_live_abc123def456" \
  -H "Content-Type: application/json" \
  -d '{
    "settings": {
      "rate_limit": 5
    }
  }'

This sets the limit to 5 submissions per IP per 60-second window for this form.

settings.rate_limit is not validated by the changeset — there is no upper bound and no type cast applied beyond what :map accepts. Set it to whatever positive integer you want; the platform pipeline cap of 200 req / 60 s per IP will still apply on top, so values above ~200 do not buy you anything in practice. If you do want to lift the per-form ceiling above the platform cap, contact support — it requires a pipeline-level change, not a settings tweak.

rate_limit: 0 does not disable rate limiting

Setting rate_limit to 0 causes the per-form RateLimiter to allow exactly one request per 60-second window per IP (the first request inserts the counter; every subsequent request fails the count >= 0 check). To run a form effectively unrate-limited at the per-form layer, set a high integer (e.g. 1_000_000) — the platform pipeline cap (200 req / 60 s per IP) will still hold the line. Removing the rate_limit key entirely falls back to the default of 10/60s, not "unlimited".

Managing Spam Submissions

Viewing Spam in the Dashboard

By default, spam submissions are hidden in the dashboard. Use the filter controls to show spam entries.

Filtering via API

Use the is_spam query parameter to filter submissions:

bash
# List only spam submissions
curl "https://app.krafter.dev/api/v1/forms/FORM_ID/submissions?is_spam=true" \
  -H "Authorization: Bearer kr_live_abc123def456"

# List only non-spam submissions (default)
curl "https://app.krafter.dev/api/v1/forms/FORM_ID/submissions?is_spam=false" \
  -H "Authorization: Bearer kr_live_abc123def456"

Marking / Unmarking Spam

You can manually mark a legitimate submission as spam or unmark a false positive:

bash
# Mark as spam
curl -X PATCH https://app.krafter.dev/api/v1/forms/FORM_ID/submissions/SUBMISSION_ID \
  -H "Authorization: Bearer kr_live_abc123def456" \
  -H "Content-Type: application/json" \
  -d '{"is_spam": true}'

# Unmark spam (false positive)
curl -X PATCH https://app.krafter.dev/api/v1/forms/FORM_ID/submissions/SUBMISSION_ID \
  -H "Authorization: Bearer kr_live_abc123def456" \
  -H "Content-Type: application/json" \
  -d '{"is_spam": false}'

Best Practices

  • Always include a honeypot field in your HTML forms. It catches the majority of automated spam with zero friction for real users.
  • Keep rate limits reasonable. The default of 10 per minute works well for contact forms. Increase it for high-traffic forms like newsletter signups.
  • Review spam periodically. Check the spam queue for false positives and unmark them if needed.
  • Combine with client-side validation. While honeypots and rate limits handle automated abuse, client-side validation (e.g., required attributes, email format checks) improves the user experience.

Built by Krafter Studio