CWaptcha is invisible to users because the protection happens entirely at the cryptographic layer — not the UI layer. Here's what happens on every form submission.
When your page loads, cwaptcha.js automatically calls GET /cwaptcha/issue. Your server responds with a JSON payload containing four values:
{
"captchaId": "a1b2c3...", // unique ID for this session
"nonce": "d4e5f6...", // 32-byte random hex
"token": "g7h8i9...", // HMAC of (captchaId|nonce|expiry|userAgent)
"fieldSalt": "j0k1l2..." // per-session key for client-side HMAC
}
The server stores captchaId → { nonce, fieldSalt, expiry, used: false } with a configurable TTL (default 5 minutes). The fieldSalt is derived from the master SecretKey and is never the key itself.
When the user submits the form, cwaptcha.js intercepts the submit event and:
The middleware (or [CWaptchaValidation] attribute) runs these checks in order before your handler is called:
| Reason | Meaning |
|---|---|
expired | Nonce not found, TTL passed, or already redeemed |
replayed | Token submitted a second time |
hash_mismatch | Field HMAC doesn't match — form was tampered |
honeypot | Hidden field was filled — likely a bot |
tampered | Token signature invalid — token was modified |
https_required | Request is not HTTPS and RequireHttps: true |