Tu servidor llama a siteverify para comprobar un token producido por el widget OpenWAF Verify. Hay dos endpoints con la misma semántica:
| Endpoint | Úsalo cuando |
|---|---|
POST https://<tu host>/__waf/challenge/siteverify | Tu backend llega a un nombre con proxy de OpenWAF. Cualquier nombre servido por el borde puede verificar un token de cualquier widget, incluso de otra zona. |
POST https://dash.openwaf.dev/v1/challenge/siteverify | Tu backend sale directamente. No hace falta token de API: el secreto del widget autentica la llamada. |
Un token es de un solo uso entre los dos: uno gastado en el borde también está gastado en el plano de control, y al revés.
Petición
El cuerpo es application/x-www-form-urlencoded o, con Content-Type: application/json, un objeto JSON. Como mucho 8192 bytes.
| Campo | Obligatorio | Significado |
|---|---|---|
secret | sí | El secreto del widget (ows_…). |
response | sí | El token del campo owaf-verify-response (v1.…, como mucho 2048 caracteres). |
remoteip | no | La dirección IP del visitante. Si se envía, tiene que estar en la misma /24 IPv4 o /64 IPv6 que el navegador que resolvió el reto. |
idempotency_key | no | Hasta 128 caracteres. Consulta Idempotencia. |
Respuesta
La respuesta es JSON y siempre 200, salvo un cuerpo mal formado (400), un método distinto de POST (405) y un límite de peticiones (429 con Retry-After).
{"success": true,
"challenge_ts": "2026-10-28T10:15:03Z",
"hostname": "example.com",
"action": "signup",
"cdata": "order-1234",
"error-codes": [],
"metadata": {"mode": "managed", "interactive": false, "risk": "low"}}| Campo | Significado |
|---|---|
success | true solo para un token válido y sin gastar de este widget. |
challenge_ts | Cuándo se emitió el token (RFC 3339, UTC). |
hostname | Nombre de host donde se mostró el widget. Comprueba que es tuyo. |
action | El data-action del widget. Comprueba que corresponde al formulario. |
cdata | El data-cdata del widget, si lo hay. |
error-codes | Vacío si todo va bien; si no, uno de los códigos de abajo. |
metadata.mode | Modo del widget: managed, non_interactive o invisible. |
metadata.interactive | true cuando el visitante pasó por el paso «Soy una persona». |
metadata.risk | low, o high cuando hizo falta interacción. |
metadata.result_with_testing_key | true cuando respondió un secreto de prueba. |
Una comprobación fallida responde {"success": false, "error-codes": ["…"]}.
Códigos de error
| Código | Significado |
|---|---|
missing-input-secret | secret está vacío. |
invalid-input-secret | El secreto está mal formado o no es un secreto vigente del widget que emitió el token. |
missing-input-response | response está vacío. |
invalid-input-response | El token está mal formado, tiene una firma incorrecta o es de un widget desconocido. |
timeout-or-duplicate | El token caducó (300 segundos) o ya se gastó. |
remoteip-mismatch | remoteip no está en la red a la que va ligado el token. |
bad-request | El cuerpo no es un formulario ni un objeto JSON válido, es demasiado grande, o el método no es POST. |
rate-limited | Demasiadas llamadas; espera lo que indique Retry-After. |
internal-error | Ahora mismo no se pudo comprobar el uso único. Reintenta; el token no se gastó. |
Idempotencia
Un token se gasta en la primera llamada correcta, así que un reintento tras un error de red recibiría timeout-or-duplicate. Envía un idempotency_key para que los reintentos sean seguros: una llamada con el mismo secreto, respuesta y clave en los 300 segundos siguientes devuelve la primera respuesta sin gastar nada otra vez.
La clave la recuerda el verificador que respondió, no se comparte: el nodo de borde que atendió la primera llamada, o el plano de control. Reintenta contra el mismo endpoint. Las respuestas internal-error nunca se recuerdan.
Límites
| Endpoint | Límites |
|---|---|
Tu host (/__waf/challenge/siteverify) | 1000 llamadas por minuto por secreto de widget, más el límite por IP del borde. |
| Plano de control | 600 llamadas por minuto por IP de cliente y 1000 por secreto de widget. |
Los endpoints del propio widget en tu host también tienen límite: 60 retos y 20 soluciones por minuto por IP del visitante.
Errores habituales
- Llamar a siteverify desde el navegador: el secreto tiene que quedarse en tu servidor. El endpoint del borde no envía cabeceras CORS.
- Aceptar un formulario cuando no se pudo llegar a siteverify. Trata cualquier respuesta distinta de
"success": truecomo un fallo. - Reutilizar un token tras un envío fallido: llama a
owafVerify.reset(id)para obtener uno nuevo.