# Protección antispam

> Honeypot, marcas de tiempo firmadas, puntuación de spam, verificación humana, dominios permitidos y límites de frecuencia.

Cada formulario tiene protección por capas sin configurar nada: un honeypot, una marca de tiempo de renderizado firmada, puntuación de spam y límites de frecuencia. Además, puedes añadir una verificación humana y limitar el formulario a tus propios dominios.

| Capa | Activa por defecto | Resultado |
| --- | --- | --- |
| [Límites de frecuencia](https://ikiform.com/es/docs/rate-limits.md) | Sí, preset Standard | `429 rate_limited` |
| [Dominios permitidos](#allowed-domains) | No | Spam o `403 origin_not_allowed` |
| [Verificación humana](#human-check) | No | `403 challenge_required` o una página de verificación |
| [Honeypot](#honeypot) | Sí | +5 a la puntuación |
| [Puntuación de spam](#scoring) | Sí | Spam desde 5 |

## Cómo se gestiona el spam

Un envío que se considera spam se guarda con el estado `spam` y aun así recibe una respuesta de éxito normal, para que los bots no aprendan nada. El spam no envía webhooks ni emails, no cuenta para tu uso mensual y se elimina a los 30 días. Revísalo en el panel o con la [API REST](https://ikiform.com/es/docs/api.md#mark-spam); al marcar un envío como no spam, se envían entonces sus webhooks.

## Honeypot

Añade un input oculto que las personas nunca rellenan. El nombre predeterminado es `_gotcha`; puedes cambiarlo por formulario en **Honeypot field**, en los ajustes del formulario. Las técnicas para ocultarlo están en [Formularios HTML](https://ikiform.com/es/docs/html.md#honeypot).

`Honeypot`:

```html
<input name="_gotcha" type="text" tabindex="-1" autocomplete="off" hidden />
```

## Puntuación de spam

Cada envío recibe una puntuación según estas señales. Con 5 o más, se guarda como spam.

| Señal | Motivo en el panel | Puntuación |
| --- | --- | --- |
| El honeypot está rellenado | `honeypot` | +5 |
| Se envió menos de 2 segundos después de renderizar el formulario | `timestamp_fast` | +3 |
| La marca de tiempo de renderizado firmada es falsa o tiene más de 24 horas | `timestamp_invalid` | +3 |
| Un enlace en un campo de nombre o de asunto | `url_in_name` | +3 |
| Se enviaron los mismos campos a este formulario en las últimas 24 horas | `duplicate` | +3 |
| Tres o más enlaces en total | `many_urls` | +2 |
| Una frase de spam conocida (22 incluidas) | `keyword` | +2 |
| Una dirección de email de un dominio de email desechable | `disposable_email` | +2 |

- La marca de tiempo de renderizado solo existe cuando el formulario lo renderiza el SDK o el [componente de shadcn/ui](https://ikiform.com/es/docs/shadcn.md), que envían un `_ikiform_ts` firmado. Los envíos HTML sin ella no se penalizan.
- Un campo de nombre es cualquier campo cuyo nombre contiene `name`, `firstname`, `lastname`, `fullname`, `surname` o `subject`, pero no palabras como `company`, `website` o `domain`.
- Dos motivos fuerzan el spam sea cual sea la puntuación: `origin_mismatch`, de los [dominios permitidos](#allowed-domains), y `captcha_unavailable` cuando no se puede conectar con Turnstile.

## Verificación humana

Cada formulario tiene un ajuste **Spam check** con tres opciones. Está desactivado por defecto, así que las integraciones existentes con `fetch` y curl siguen funcionando cuando creas un formulario.

| Opción | Qué hace |
| --- | --- |
| Desactivada | Sin desafío. El honeypot, la marca de tiempo y la puntuación siguen funcionando. |
| Ikiform (recomendada) | Cloudflare Turnstile ejecutado por Ikiform en ikiform.com. No necesitas cuenta ni claves de Cloudflare. |
| Tus propias claves de Turnstile | Tu widget de Turnstile y tu clave secreta. El hostname del token se compara con tus dominios permitidos. |

### Cómo funciona la verificación de Ikiform

Cómo se obtiene el token depende de cómo envías:

- **Envío de formulario HTML:** el envío sin token recibe una breve página "Checking you're human" en ikiform.com. Ejecuta Turnstile y vuelve a enviar los campos originales con el token, y luego el navegador sigue tu redirección como siempre.
- **JavaScript:** llama a `challenge(formId)` del SDK. Monta un pequeño frame de ikiform.com que permanece invisible salvo que Turnstile necesite un clic, y se resuelve con un token. Pásalo a `submit` como `turnstileToken`. Un envío con `fetch` sin token recibe `403 challenge_required`.
- **Componente de shadcn/ui:** se encarga por ti.
- **Servidor con una clave secreta:** la verificación se omite, porque un servidor no puede resolverla.

`form.js`:

```js
import { challenge, submit } from "@ikiform/sdk";

const form = document.querySelector("form");

form.addEventListener("submit", async (event) => {
  event.preventDefault();
  const turnstileToken = await challenge("frm_xxxxxxxxxxxxxxxx");
  const result = await submit("frm_xxxxxxxxxxxxxxxx", new FormData(form), { turnstileToken });
  if (result.ok) form.replaceWith("Thanks, we got it.");
});
```

Los tokens son de un solo uso y duran 300 segundos, así que obtén uno por envío. Un envío JSON sin token recibe `403 challenge_required` con un `challenge_url`; un token que no pasa la verificación recibe `403 captcha_failed`.

`403 Forbidden`:

```json
{
  "ok": false,
  "error": {
    "code": "challenge_required",
    "message": "Complete the human check, then send the form again.",
    "challenge_url": "https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx/challenge"
  },
  "request_id": "req_01k7v2c9t3e8m5q4x6z0a1b2c3"
}
```

> Si no se puede conectar con el propio Turnstile, el envío se acepta y se guarda como spam con el motivo `captcha_unavailable`, para que puedas recuperarlo en lugar de perderlo.

### Tus propias claves de Turnstile

Pega tu clave de sitio y tu clave secreta en los ajustes del formulario. El secreto se guarda cifrado. Renderiza el widget en tu formulario para que añada `cf-turnstile-response` al envío, o pasa el token a `submit` como `turnstileToken`. Los envíos sin un token válido reciben `403 captcha_failed`, incluidos los envíos con una clave de API secreta.

## Dominios permitidos

Indica los dominios donde está tu formulario en **Allowed domains**. A partir de ahí, un envío debe venir de uno de ellos o de un subdominio, según la cabecera `Origin`, o el `Referer` cuando no hay origen. Deja la lista vacía para aceptar envíos desde cualquier sitio.

| Dominios permitidos | Envío desde | Coincide |
| --- | --- | --- |
| `example.com` | `https://example.com` | Sí |
| `example.com` | `https://www.example.com` | Sí, subdominio |
| `example.com` | `http://localhost:3000` | No, añade `localhost` mientras desarrollas |
| `example.com` | `https://example.co` | No |
| `app.example.com` | `https://example.com` | No, un dominio padre no coincide |

**When a post comes from another site** decide qué pasa cuando no coincide: **Mark as spam** (por defecto) o **Reject** con `403 origin_not_allowed`. Los envíos con una clave de API secreta se saltan esta comprobación. Los dominios permitidos también deciden qué destinos de `_redirect` se siguen y dónde se puede incrustar el frame de verificación de Ikiform.

## Límites de frecuencia

Cada formulario tiene un preset de **Rate limit**, contado con exactitud por IP de visitante y formulario. Standard permite 5 envíos por minuto, Relaxed 20 por minuto y Strict 2 cada 10 minutos. Las reglas completas están en [Límites de frecuencia](https://ikiform.com/es/docs/rate-limits.md).
