# Spam Protection

> CAPTCHA, honeypot fields, and language detection to keep spam out.

import ThemeImage from '../../components/ThemeImage.astro';

StaticForm includes multiple layers of spam protection that work together to block unwanted submissions.

## CAPTCHA

StaticForm supports the following CAPTCHA providers:

| Provider | Description |
|----------|-------------|
| **reCAPTCHA v3** | Google's invisible CAPTCHA, no user interaction needed |
| **reCAPTCHA v2** | Google's classic "I'm not a robot" checkbox |
| **hCaptcha** | Privacy-focused alternative to reCAPTCHA |
| **Turnstile** | Cloudflare's CAPTCHA, fast and privacy-friendly |

### Setup

1. Get a site key and secret key from your CAPTCHA provider
2. In your form settings, select the CAPTCHA type and enter the **secret key** (both reCAPTCHA v2 and v3 use the **reCAPTCHA** option)
3. Add the CAPTCHA script and widget to your frontend
4. On submit, send the resulting token to StaticForm under the field name the provider expects (see below)

### How CAPTCHA tokens reach StaticForm

When a CAPTCHA is enabled on a form, StaticForm expects every submission to include a token field in the POST body. The field name depends on the provider:

| Provider | Form-data field |
|----------|-----------------|
| reCAPTCHA v2 / v3 | `g-recaptcha-response` |
| Cloudflare Turnstile | `cf-turnstile-response` |
| hCaptcha | `h-captcha-response` |

If the field is missing or the token fails verification, the submission is rejected with a CAPTCHA error.

How the token gets there depends on which provider you use:

- **reCAPTCHA v2, Turnstile, and hCaptcha** all render a widget into your page (a checkbox or invisible challenge). Once the user solves it, the provider's script automatically injects a hidden `<input>` with the right name and value into the surrounding `<form>`. As long as the widget lives **inside** the form, a regular HTML submit or a `new FormData(form)` call will pick the token up automatically. You don't have to write any JavaScript.

- **reCAPTCHA v3** is invisible and has no widget, so there is no hidden input. You have to call `grecaptcha.execute(siteKey, { action })` yourself, get back a token, and append it to your form data as `g-recaptcha-response` before submitting.

In short: for the three widget-based providers, dropping the right `<div>` inside your form is enough. For reCAPTCHA v3, you have to wire up the token injection in JavaScript.

### Use the JavaScript helper if you'd rather not handle this yourself

If you don't want to manage scripts, widgets, and token plumbing by hand, the [StaticForm JavaScript helper](/docs/javascript-helper#captcha-integration) does it for you. It supports all four providers, **auto-loads the provider's `<script>` tag for you**, programmatically generates a fresh reCAPTCHA v3 token on every submit, picks up the token from the rendered widget for v2/Turnstile/hCaptcha, and resets the widget after each submission so users can submit again:

```javascript
// reCAPTCHA v3 (invisible, generates tokens programmatically)
StaticForm.attach(form, {
  endpoint: 'https://api.staticform.app/api/v1/forms/YOUR_FORM_ID',
  captcha: { type: 'recaptcha-v3', siteKey: 'YOUR_SITE_KEY' },
});

// reCAPTCHA v2 / Turnstile / hCaptcha
// (render the provider's widget inside your form, then:)
StaticForm.attach(form, {
  endpoint: 'https://api.staticform.app/api/v1/forms/YOUR_FORM_ID',
  captcha: { type: 'turnstile' }, // or 'recaptcha-v2' or 'hcaptcha'
});
```

See the [JavaScript helper docs](/docs/javascript-helper#captcha-integration) for the full setup (script tags, widget markup, and config) of each provider.

## Honeypot

A honeypot is a hidden field that real users won't fill out but spam bots will. If the field has a value, the submission is flagged as spam.

### Setup

Enable the honeypot in your form settings and choose a field name (default: `website_url`). Then add a hidden field to your HTML:

```html
<div style="display: none;" aria-hidden="true">
  <input type="text" name="website_url" tabindex="-1" autocomplete="off" />
</div>
```

> **Tip:** Use a field name that looks like a real field (like `website_url`) to trick bots into filling it out.

## Language Detection

Language detection analyzes the text content of submissions and flags those that don't match your expected languages.

### Setup

1. Enable language detection in your form settings
2. Set your **expected languages** using [ISO 639-2/T codes](https://www.loc.gov/standards/iso639-2/php/code_list.php) (3-letter, e.g. `eng`, `deu`, `fra`)
3. Optionally specify which fields to check (defaults to all text fields)

This is useful if your form targets a specific audience and you want to filter out submissions in unexpected languages.

## IP-Based Analysis

For client-side submissions, StaticForm performs IP-based analysis to detect known spam sources. This runs automatically and requires no configuration.

> **Note:** IP-based analysis is skipped for server-side submission mode since server IPs are expected.

## Spam Management

All submissions include a spam probability score. In the dashboard you can:

- View spam scores and detection categories for each submission
- Manually mark submissions as spam or legitimate
- View separate spam and legitimate submission lists

<ThemeImage src="/screenshots/sf-spam-detail-light.png" srcDark="/screenshots/sf-spam-detail-dark.png" alt="Spam detection details for a submission" />