This is the form your visitors will see
Clean, fast, no CAPTCHA. Try it — this is a real working demo.
What happens next ⚡
- 📥Submission received — stored in your searchable dashboard
- 📧Email notification — delivered to your inbox instantly
- 🛡️Spam filtered — AI classifier + honeypot, no CAPTCHA
- 🔗Webhook fired — optional: forward to Slack, Discord, Sheets
- ↩️User redirected — to your thank-you page
500 submissions/month free · No credit card
The contact form threat model
Before implementing defenses, understand what you're defending against. A public contact form is a POST endpoint anyone on the internet can hit. The threats:
- Spam bots — automated scripts that submit forms with junk, phishing links, or promotional content. Volume ranges from annoying to overwhelming (100+ per day on unprotected forms).
- XSS injection — attackers submit
<script>tags or JavaScript payloads in form fields, hoping you'll render them unescaped in an admin dashboard or public page. - SQL injection — if your backend concatenates form values into SQL queries without parameterization, attackers can extract or destroy your database.
- CSRF — an attacker tricks an authenticated user into submitting a form to your server, performing an action they didn't intend.
- Data interception — form data transmitted over HTTP (not HTTPS) can be intercepted by anyone on the network path.
- PII leakage — form submissions contain visitor names, emails, and sometimes phone numbers. Mishandling this data violates GDPR, CCPA, and other privacy regulations.
- Abuse and harassment — malicious users submit threatening or harassing content through your form.
The good news: each of these has a well-established defense. Let's go through them.
1. HTTPS: non-negotiable transport encryption
Serve your form page over HTTPS, and submit to an HTTPS endpoint. This encrypts the data in transit — between the visitor's browser and your server (or the form backend). Without HTTPS, form data travels as plaintext and can be intercepted by anyone on the same network (public WiFi, ISP, coffee shop).
In 2026, there's no excuse for HTTP. Free SSL certificates are available from Let's Encrypt, Cloudflare, and every major hosting provider. Browsers flag HTTP pages as "Not Secure," and form autofill is disabled on HTTP pages.
<!-- Correct: HTTPS form serving to HTTPS endpoint -->
<form action="https://splitforms.com/api/submit" method="POST">
<!-- form fields -->
</form>
<!-- Wrong: HTTP (insecure) -->
<form action="http://example.com/api/submit" method="POST">
<!-- browser will warn, data is plaintext -->
</form>2. Server-side input validation
Never trust data from the browser. Client-side validation (HTML required, type="email", JavaScript checks) improves UX but provides zero security — bots bypass it trivially by POSTing directly to your endpoint.
Validate on the server:
- Email format — use a regex or built-in validator. Reject obviously invalid addresses (
test@test,foo@bar..com). - Field length — cap name at 100 chars, message at 5,000 chars. Bots submit megabytes of text to exhaust server resources.
- Required fields — reject submissions missing required fields.
- Field allowlist — only accept fields you defined. Drop unexpected fields. This prevents parameter injection.
- Content type — reject HTML in text fields (or sanitize aggressively). Strip
<script>,<iframe>, andjavascript:URLs.
// Server-side validation (Node.js)
function validateSubmission(data) {
const errors = [];
// Email validation
if (!data.email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email)) {
errors.push('Valid email required');
}
// Name length
if (!data.name || data.name.length > 100) {
errors.push('Name required, max 100 characters');
}
// Message length
if (!data.message || data.message.length > 5000) {
errors.push('Message required, max 5000 characters');
}
// Strip HTML tags from all string fields
for (const [key, value] of Object.entries(data)) {
if (typeof value === 'string') {
data[key] = value.replace(/<[^>]*>/g, '');
}
}
return errors;
}3. XSS prevention: always escape output
XSS attacks happen when you display user-submitted content without escaping HTML. The visitor types <script>alert(document.cookie)</script> in the message field. If you render that in your admin panel without escaping, the script executes in your browser and can steal cookies or make authenticated requests.
The fix is simple: always escape user input when rendering it as HTML. Every modern framework does this by default:
- React: JSX escapes all content inside
{}automatically.<div>{userInput}</div>is safe. OnlydangerouslySetInnerHTMLis dangerous. - Vue: Double curly braces
{{ }}escape automatically. Onlyv-htmlis dangerous. - PHP: Use
htmlspecialchars($input, ENT_QUOTES, 'UTF-8'). - Python/Django: The template engine auto-escapes. Use
safefilter only for trusted content.
Never use innerHTML, dangerouslySetInnerHTML, or v-html with user-submitted form data. If you must render rich text from users, use a sanitizer like DOMPurify.
4. Spam protection: layered defense
Spam is the most common form security problem. A multi-layered approach works best:
- Honeypot field — a hidden field that bots fill automatically. If it's filled, silently drop the submission. Zero UX impact on real visitors.
- Time trap — record when the page loaded. If the form is submitted in under 3 seconds, it's a bot. Real humans take at least 5-10 seconds to read and fill a form.
- Rate limiting — cap submissions per IP address (e.g. 5 per hour). See form submission rate limiting for implementation details.
- AI content filtering — an AI model scores each submission for spam probability. Catches sophisticated bots that mimic human behavior. Splitforms includes this on every plan.
- CAPTCHA (last resort) — only if the above layers aren't enough. CAPTCHAs hurt conversion rates and accessibility. See best CAPTCHA for contact forms.
For the full deep dive, see how to stop contact form spam and the complete spam protection guide.
5. Rate limiting: stop abuse at the network layer
Rate limiting caps how many submissions a single IP address can send in a time window. This prevents:
- Spam floods — bots submitting hundreds of forms per minute.
- Resource exhaustion — attackers filling your database or email quota.
- Harassment — repeated threatening submissions from one source.
A practical rate limit for contact forms: 5 submissions per IP per hour, 20 per day. Splitforms applies rate limiting automatically on all plans. If you run your own server, use express-rate-limit (Node.js), django-ratelimit (Django), or Nginx limit_req.
See form submission rate limiting for the complete implementation guide.
6. Encryption at rest and PII handling
Form submissions contain PII — names, email addresses, sometimes phone numbers. You have a legal obligation (GDPR, CCPA) to protect this data:
- Encryption in transit — HTTPS (covered above).
- Encryption at rest — the database storing submissions must be encrypted. Managed databases (RDS, Supabase, PlanetScale) do this by default.
- Access control — only authorized team members can view submissions. Splitforms requires authentication to view the dashboard.
- Data retention — define how long you keep submissions. Auto-delete after 90 days, or let users request deletion. See form data retention policy.
- Privacy policy — link to your privacy policy below the form. Disclose what data you collect, why, and how long you keep it.
For more, see GDPR-compliant form submissions.
How splitforms handles security
If you use splitforms as your form backend, most of these security layers are handled for you:
- HTTPS endpoint —
https://splitforms.com/api/submitwith TLS 1.3. - Server-side validation — field length limits, email format checks, HTML stripping.
- Honeypot + time trap — built into every form, no configuration needed.
- AI spam filtering — every submission is scored by a language model trained on millions of spam/ham examples.
- Rate limiting — per-IP limits applied automatically. Custom limits on Pro.
- Encryption at rest — submissions stored with AES-256 encryption.
- GDPR compliance — data processing agreement available, EU data residency option on Pro.
Plans: Free 500 submissions/month with all security features, Starter $1/mo for webhooks, Pro $5/mo for custom rate limits and EU residency. Get started free or email hello@splitforms.com with security questions.
FAQ
Are HTML contact forms secure?
A plain HTML form is not inherently secure. The form transmits data in plaintext unless served over HTTPS, has no built-in spam protection, sends data to any endpoint in the action attribute (including third-party servers), and trusts whatever the visitor types. Security must be added: HTTPS for transport encryption, server-side validation for data integrity, CSRF tokens for authenticated forms, honeypot + rate limiting for spam, and proper output escaping when displaying submissions in a dashboard.
Do I need CSRF protection on my contact form?
CSRF (Cross-Site Request Forgery) protection is necessary when the form submits to an authenticated endpoint — i.e. the server uses cookies to identify the user. For public contact forms that anyone can submit without logging in, CSRF protection is less critical because there's no session to hijack. However, if your form endpoint accepts session cookies (e.g. it's part of an authenticated app), you must use CSRF tokens. The splitforms public endpoint doesn't use cookies, so CSRF doesn't apply.
How do I prevent XSS in contact form submissions?
XSS (Cross-Site Scripting) happens when you display user-submitted content without escaping it. The visitor types <script>alert('hack')</script> in the message field, and if you render that in your admin dashboard or a public comment section without escaping, the script runs. The fix: always escape HTML when rendering user input. In React, this is automatic (JSX escapes by default). In server-rendered templates, use the escape filter. Never use innerHTML or dangerouslySetInnerHTML with raw user input.
Should I encrypt contact form submissions at rest?
If you store submissions in your own database, encryption at rest is a best practice and increasingly a compliance requirement (GDPR Article 32, CCPA). Most managed databases (Postgres on RDS, Supabase, PlanetScale) encrypt at rest by default. If you use a hosted form backend like splitforms, submissions are stored encrypted in transit (TLS) and at rest (AES-256). You don't have to manage encryption yourself.
What PII should I collect in a contact form?
Collect only what you need to respond. Typically: name and email are sufficient. If you need a phone number for callbacks, ask for it but don't require it. Avoid collecting sensitive PII (Social Security numbers, credit card numbers, health information) in a contact form — that data requires PCI-DSS or HIPAA compliance, which standard contact forms don't provide. Include a privacy policy link below the form disclosing what you collect and why.
More security reads: stop contact form spam, GDPR compliance, data retention, all posts.