The form POSTs to an MVC controller action decorated with [CWaptchaValidation].
No AJAX, no ProtectedPaths config entry needed — protection is per-action via the attribute.
How it works:
data-cwaptcha on the form triggers cwaptcha.js to intercept the submit event,
inject the CAPTCHA fields into the form, then allow the browser to POST normally.
On success the controller redirects back here; on failure the browser shows the JSON error response.
| Cooldown | 120s base → ×2 per retry during lockout → capped at 3600s |
| Challenge fallback | BypassBehavior, window 120s, tolerance 6px |
| Min fill time | 3000 ms |
| Require mouse movement | True |
| Anomaly trigger threshold | 0.75 |
| Response mode | TooManyRequests429 |
| Behavior memory TTL | 3600 s |
| Honeypot field | cw_hp_email |
| Nonce TTL | 300 s |
A behavior_flagged rejection right after a successful submit means the
cooldown above is active — each retry during the lockout extends it. Wait it out
(the AJAX/fetch demos show the remaining seconds), or lower
CWaptcha:Behavior:CooldownSeconds in appsettings.json while testing.
[Route("contact-mvc")]
public sealed class ContactMvcFormController : Controller
{
[HttpPost("submit")]
[CWaptchaValidation]
public IActionResult Submit(
[FromForm] string? name,
[FromForm] string? email,
[FromForm] string? message)
=> Redirect($"/ContactMvc?success=1&name={Uri.EscapeDataString(name ?? "")}" +
$"&email={Uri.EscapeDataString(email ?? "")}" +
$"&message={Uri.EscapeDataString(message ?? "")}");
}