Official server-side verification SDK for Krynox Captcha. Java 17+, one small dependency (Gson).
<dependency>
<groupId>net.krynox</groupId>
<artifactId>krynox-captcha</artifactId>
<version>0.1.0</version>
</dependency>import net.krynox.captcha.KrynoxCaptcha;
KrynoxCaptcha krynox = new KrynoxCaptcha(System.getenv("KRYNOX_SECRET"));
// in your request handler
KrynoxCaptcha.Result r = krynox.verify(token, request.getRemoteAddr());
if (!r.success()) {
response.sendError(400, "Captcha verification failed");
return;
}
// optional: privacy-preserving risk hint + explainable reason codes
if ("high".equals(r.risk()) || r.reasons().contains("tor-exit")) {
// add friction (email verification, manual review, …)
}r.reasons()— stable codes explaining the score ("tor-exit","elevated-request-rate", …).r.agent()— non-null when a verified AI agent (Web Bot Auth) was forwarded:.verified(),.name(),.allowlisted(). Allowlist good bots instead of blocking them.r.human()— non-null when a device-attested human (Private Access Token) was forwarded:.attested(),.method(),.issuer().
if (r.agent() != null && r.agent().verified() && r.agent().allowlisted()) { /* trusted crawler */ }
if (r.human() != null && r.human().attested()) { /* proven human, skip friction */ }KrynoxCaptcha.Classification c = krynox.classify(comment, clientIp);
// or score a whole form: classifyFields(Map.of("name", name, "message", comment), clientIp)
if (c.blocked() || "BAD".equals(c.classification())) {
response.sendError(400, "rejected");
}Transient failures (network, 429, 5xx) are retried automatically (default 2, exponential
backoff; 4th constructor arg). A retried verify carries an idempotency key so it never fails
the single-use token — the server replays the first outcome.
// a real user got blocked by mistake → un-block their IP
KrynoxCaptcha.Feedback fb = krynox.feedback("human", clientIp, "support ticket #1234");
// confirm a bot you let through
krynox.feedback("bot", suspiciousIp, null);new KrynoxCaptcha(secret)ornew KrynoxCaptcha(secret, endpoint, Duration timeout[, int retries])verify(response[, remoteip[, idempotencyKey]]) → Resultclassify(text, ip)/classifyFields(Map<String, String> fields, ip) → Classificationfeedback(label, ip, note) → Feedback—labelis"human"or"bot"
Result: success(), score(), risk(), hostname(), challengeTs(), action(), cdata(), errorCodes(), reasons(), agent(), human()
Classification: ok(), score(), classification(), reasons(), blocked(), errorCodes().
Error codes: KrynoxCaptcha.ErrorCode.RATE_LIMITED, etc.
Self-hosting? Pass an endpoint like https://captcha.your-domain/siteverify, or just the base
URL https://captcha.your-domain. classify and feedback derive their URLs from it: a
/siteverify suffix is replaced, otherwise the path is appended.
Every request carries User-Agent: krynox-captcha-java/0.1.0 (KrynoxCaptcha.USER_AGENT), so
API traffic is attributable to SDK and version. Gson stays an implementation detail — it never
appears in the public API.
MIT licensed. Docs: https://docs.krynox.net