Build in Public · LF-15
Selecting a Captcha API: From Type Directories to Verification Loops in Custom Systems
Using custom systems and official testing mechanisms as scope, verify types, parameters, and result shapes. Separate the acceptance of tokens, server-side validation, business operations, and costs to avoid treating return values as successful validation.
When integrating captchas, the most dangerous misjudgment is rarely choosing the wrong name—it is treating "obtaining a result" as "verification passed." A client receiving a non-empty token, a verification service accepting that token, and your business logic permitting the operation are three distinct events. Verifying only the first leaves forms vulnerable to backend rejection; treating the first directly as the third risks bypassing internal permission checks.
This guide targets development and testing in custom systems: how to identify challenge products, read current type contracts for EveryInfra, isolate test configurations, and verify that systems halt at the correct state upon failure. Official testing environments and explicitly authorized test scopes also apply. This article does not provide steps for operating real third-party accounts, payments, permissions, or risk challenges.
Determine What to Test First
If the goal is simply to confirm that your form saves data after successful validation and blocks writes upon failure, prioritize the official testing mechanisms provided by the challenge product. These produce controllable outcomes without turning every CI run into a live challenge solve. Introducing another API at this stage adds no testing value—it only creates an extra dependency requiring independent acceptance.
If you must evaluate the captcha capabilities of EveryInfra, split the test into two independent questions: whether the API inputs and outputs match the agreed shape, and whether return values are correctly handled in authorized environments. Keep their evidence separate without substituting the success of one for the other. The starting point for selection is the testing task, not the number of types listed in a directory.
Identify Products by Integration Config, Not Appearance
Confirm products, modes, domains, and use cases using integration code, backend configurations, and corresponding official documentation from your own pages. A visual checkbox alone does not determine request types, and different modes of the same product should not be swapped arbitrarily. Keep a clear test record: who owns the page, what is permitted for testing, which test configurations are used, and what the expected outcomes are.
For authorized environments you do not maintain, parameters must be provided by the environment maintainer or obtained via authorized documentation. Stop when context is missing and ask the maintainer to confirm; never let programs guess similar types or fabricate fields. Seeing a public identifier on a page does not grant permission for arbitrary automated actions.
For example, the Turnstile hostname configuration accepts domain names without protocols, ports, or paths, and configuring a parent domain covers its subdomains. Test environments must have scopes verified by maintainers rather than relaxing configurations to pass an incorrect address—this differs from website_url fields in requests.
Read Types, Availability, Parameters, and Result Shapes
Below is a free, unauthenticated directory read that creates no solving tasks. The example selects only two types to compare contracts, not to mandate operations on both challenges. Requires curl and jq; if directory reading fails, handle the discovery phase first and do not proceed with empty outputs for business requests.
set -o pipefail
curl -fsS --max-time 30 'https://api.everyinfra.com/api/v1/captcha/types' \
| jq -e '[.types[] |
select(.type == "turnstile" or .type == "recaptcha_grid") |
{type, available, required_params, optional_params, solution}
] | if length == 2 then . else error("expected types missing") end'In this public observation on 2026/9/4, required parameters for turnstile were website_url and website_key, with optional parameters including action, cdata, and page_data; solution.shape was token, with the field being token. Required parameters for recaptcha_grid were body and question, with shape as points and fields as objects. Both were available at the time. This is a directory declaration, not proof of success for a real challenge.
Therefore, result handlers should branch based on verified type and solution contracts rather than unconditionally taking top-level tokens or coercing arbitrary objects into strings. Parameters appearing in optional_params merely indicate contract declarations; whether a combination succeeds depends on current errors and availability, not by stripping essential business constraints to force success.
Save the observation timestamp alongside selected entries. Do not submit when available is false; treat missing fields, incorrect types, or read failures as unknown and halt. Even when true, availability indicates only that trying the capability was possible during observation, without guaranteeing subsequent task success.
Isolate Official Test Mechanisms from Production Configs
Cloudflare Turnstile provides test sitekeys and secrets to simulate passing, failing, or consumed tokens. Test and production tokens or keys must never be mixed. Google reCAPTCHA FAQs outline separate v2 test keys and v3 test configs; v3 test environment scores must not be treated as risk performance under live traffic.
Sitekeys are public identifiers used by pages, while secrets are backend validation keys. Test configurations must be read from explicit environment settings rather than switching to always-pass behavior simply because a request claims to be a test. Check configuration sources and environment consistency at production startup; retain only necessary audit states in logs without outputting secrets, full tokens, or raw request objects.
Place validation services behind a narrow interface in business code: business logic receives explicit verification results, while tests can inject synthetic results. Real external validation remains implemented on the server side. Injection mechanisms must be controlled exclusively by test builds or server configurations, never exposed as user-selectable request parameters. This is an application design recommendation, not an automated feature provided by EveryInfra.
Accept Tokens, Server Verification, and Business Logic Separately
Turnstile officially requires server-side calls to Siteverify; frontend components alone do not constitute complete protection. Official documentation specifies that tokens are valid for five minutes and can only be verified once. Never treat tokens as long-cacheable or replayable credentials, and do not rely on endless retries of the same value to recover from network failures.
Even when server validation succeeds, business logic must still execute session checks, resource permissions, input validation, and duplicate submission controls. Captchas are neither account credentials nor transaction authorizations. Preserve business errors when subsequent steps fail rather than renaming them "captcha failures" to spawn unlimited new tasks.
Verification details cannot be copied across products. Google reCAPTCHA backend validation documentation specifies a two-minute single-use token rule and errors such as timeout-or-duplicate, differing from Turnstile's five-minute window. Error handling must be bound to specific products and actual validation responses.
Check Backend Acceptance Conditions with Offline Examples
Below is a result checker for a custom test page. The page explicitly configures hostname as localhost and action as test; expected values come from fixed server configurations rather than user requests. In real integrations, incoming results must be fetched by your own server calling official verification endpoints—never blindly trust success submitted by clients.
function acceptOwnTestVerification(result, expected) {
if (!expected || typeof expected.hostname !== "string" ||
!expected.hostname.trim() || typeof expected.action !== "string" ||
!expected.action.trim()) {
throw new Error("server-side test configuration required");
}
if (!result || typeof result !== "object" || Array.isArray(result)) {
return { accepted: false, reason: "invalid_response" };
}
if (result.success !== true) {
return { accepted: false, reason: "verification_failed" };
}
if (result.hostname !== expected.hostname) {
return { accepted: false, reason: "hostname_mismatch" };
}
if (result.action !== expected.action) {
return { accepted: false, reason: "action_mismatch" };
}
return { accepted: true, reason: "verification_only" };
}
// all values are self-made test fixtures; no network requests, no tokens held or generated.
const expected = { hostname: "localhost", action: "test" };
const synthetic = { success: true, hostname: "localhost", action: "test" };
console.log(acceptOwnTestVerification(synthetic, expected));
// { accepted: true, reason: 'verification_only' }This example outputs verification_only: it emits no tokens, calls no Siteverify, and submits no forms. The checker enforces action because this test configuration explicitly requires it; do not assume all challenge products return identical fields. Implement validation contracts per product rather than reusing a generalized function with relaxed checks.
Test success, false success, string success values, domain errors, missing actions, empty responses, and missing server configs individually. Then verify the complete path from page to custom backend within authorized test integrations. Passing offline functions proves only those branch judgments, not network verification, production configs, or risk identification efficacy.
Determine Next Steps Based on Failure Location
- Input phase: mismatched types, missing required values, or unsupported parameter combinations. Fix contracts first; do not loop identical requests.
- Capability phase: unknown directory or currently unavailable. Stop testing and record observations without swapping challenge products arbitrarily.
- Call phase: record explicit failures and timeouts separately. Unreceived results do not mean the server failed to execute; preserve request correlation identifiers first.
- Validation phase: custom backend rejects results. Record safe error classifications and corresponding test cases, checking environments, domains, and actions instead of relaxing acceptance criteria.
- Business phase: verification passes but rejections still occur due to inputs, permissions, or duplicate submissions. Handle via business workflows without pretending the captcha remained unsolved.
Costs must also be reconciled across these layers. The current EveryInfra implementation includes accounting refund branches after solving failures, but client-side verification failures or business rejections do not equate to gateways determining solve failures. Do not deduce refunds or reserve restorations from frontend red text alone; reconcile identical requests using call responses and accessible billing records. This article introduces no new balance recovery samples for real failures, nor does it promise that all subsequent rejections alter accounting states.
Leave Reviewable Test Records
A useful acceptance record details: authorized environments, product modes, observation timestamps, test configuration categories, input structures, response structures, validation judgments, business judgments, and billing judgments. Omit real content where possible, using masked correlation IDs to tie layer results together. Explicitly document uncovered areas to prevent future teams from mistaking "directories read successfully" for complete acceptance.
Verify your own success and failure flows using official test mechanisms, evaluate necessary API branches, and only then expand verification within authorized scopes. The standard for selecting captcha types is not name similarity or a valued response, but clear input conditions, interpretable return shapes, correct backend judgments, and failure handling that stays within business boundaries.