API reference

Endpoints, response fields, status codes, rate limits and our stability commitments.

API reference (V2)

Base URL: https://api.captchacore.eu — every endpoint answers with JSON. If you use the supplied widget you never call /challenge yourself; the widget does that. For your own plugin only /verify is mandatory.

Authentication

Endpoint Key Used where
GET /api/v2/challenge cc_pub_… in the browser — may be public
POST /api/v2/verify cc_sec_… server side only — never ship it
GET /api/v2/status cc_sec_… server side only

The key always goes in the X-CaptchaCore-Key header, never in the URL.

GET /api/v2/challenge

Requests a computation together with its policy. Optional query parameter: form_type (see form policies). Answers with HTTP 200.

{
  "challenge_id": "ch_a45cd15b0d6d2dc6",
  "nonce": "a45cd15b…1d50",          // einmalig, 5 Minuten gültig
  "expires_at": 1788860066,
  "policy": {
    "mode": "adaptive",
    "challenge_type": "sha256_pow",     // Zusatzprüfung steht ggf. unter "step_up"
    "challenge_types": ["sha256_pow"],
    "difficulty": 4,                  // führende Nullen im Hash
    "algorithm": "sha256",
    "time_budget_ms": 500,
    "requires_interaction": false,
    "requires_stepup_on_submit": false,
    "memory_hard_enabled": false,
    "form_type": "contact",
    "site_profile": "balanced",
    "uam_level": 0              // 0–3, Under-Attack-Stufe
  },
  "bindings": {                       // bindet den Token an Site, Origin und Formular
    "site_id": 2,
    "origin": "https://example.com",
    "form_type": "contact",
    "issued_at": 1788859766,
    "expires_at": 1788860066,
    "token_ttl_sec": 300
  },
  "bindings_signature": "CBkATjVc…RDg==",  // Ed25519, unveränderlich mitschicken
  "signing_key_id": "k_397d99d39156c5f5",
  "widget": { /* im Admin gepflegtes Erscheinungsbild */ },
  "widget_version": "3.0.0"
}

Important for your own clients: bindings and bindings_signature must travel back into the token unchanged. If they are missing or altered, /verify answers with block.

POST /api/v2/verify

Checks the token from the form. As a rule it answers with HTTP 200, including for a rejection. Evaluate the body, not the status code.

// Request
{
  "token": "<Wert des Feldes captchacore_token>",
  "form_type": "contact",      // optional, steuert die Policy
  "page_url": "https://…",      // optional, nur für Auswertungen
  "client_ip": "203.0.113.42"  // empfohlen: IP des Besuchers
}

// Response
{
  "valid": true,               // false bei block UND bei step_up
  "decision": "allow",        // allow | challenge | step_up | block
  "action": "allow",          // identisch zu decision (älterer Feldname)
  "request_id": "…",          // für Rückfragen an den Support
  "risk_score": 8,             // 0–100, höher = riskanter
  "confidence": 0.94,          // 0.0–1.0 Signalabdeckung
  "reasons": ["pow_valid", "behavior_human_like"],
  "step_up": null             // bei decision=step_up: Daten für die Zusatzprüfung
}

How to handle decision

decision valid Recommended behaviour
allow true Process the form normally.
challenge true Let it through but log it. Optionally add a check of your own, such as a confirmation email.
step_up false Do NOT reject outright. The widget performs the additional check itself; return the form to the user with a friendly message.
block false Reject. Show an error without details so attackers learn nothing.

GET /api/v2/status

Availability and the site's current under-attack level. Suitable for a health check inside your plugin.

{ "status": "ok", "under_attack": false, "version": "2.x" }

HTTP status codes

Code Body Cause and response
200 {"valid": …} Normal case, including a rejection. Evaluate the body.
401 {"error":"Missing API key"} The X-CaptchaCore-Key header is missing.
401 {"error":"Invalid API key"} Key wrong, revoked or for the wrong site. Also the case when the public key is used on /verify.
403 {"error": …} The origin is not among the site's allowed domains.
200 {"valid": false, "reasons": ["invalid_request"]} Required field token is missing or unusable.
200 {"valid": false, "reasons": ["rate_limited"]} The site's verify limit has been reached. Reject, do not let through.
429 — Only on /challenge (limit per visitor IP). Above the limit, verify answers with 200 and "rejected".
401 {"error":"Invalid API key"} Also when the organisation has been deleted or deactivated: its keys are then no longer valid.
503 {"error":"Account suspended"} Account suspended because of an outstanding payment. Only /verify and /status; challenges keep working. Protection is suspended, and your integration applies its fail-open rule so that you can still log in.
5xx — A fault on our side. Your fail-open or fail-closed rule applies.

Rate limits

Endpoint Limit Counted by
GET /api/v2/challenge300 / minVisitor network and site (IPv6 per /64); 1200 / min per network across all sites
POST /api/v2/verify600 / mincustomer's site key
POST /api/v2/verify30 / minSite key and visitor IP (client_ip, IPv6 per /64)

Verify is counted per site, not per IP. Your server may therefore check as many forms as your site has traffic for, without locking itself out.

Only tokens for a challenge that was actually issued count towards the per-site limit. Junk requests from an attacker hit the per-visitor-IP limit and do not use up the budget of your real visitors. For this, send client_ip, as all official integrations do.

Webhooks

CaptchaCore notifies your own systems when something happens you want to react to: quota reached, under-attack mode triggered, account blocked or unblocked. Set it up in the customer area under Sites → Webhooks (from the Professional plan).

Events

EventWhen
usage.warningMonthly quota 80 % or 90 % used
usage.limit_reachedMonthly quota reached — further verifications are rejected
site.under_attack.activatedUnder-attack mode of a site activated (manually or automatically)
site.under_attack.deactivatedUnder-attack mode of a site ended
organisation.blockedAccount blocked (e.g. unpaid invoice) — API calls are rejected
organisation.unblockedAccount unblocked again
webhook.testTest event from the customer area

What arrives

A POST with a JSON body. Three headers help you match and verify it:

POST /ihr-endpunkt HTTP/1.1
Content-Type: application/json
X-CaptchaCore-Event: usage.limit_reached
X-CaptchaCore-Delivery: 5f1c2c1e-…            // eindeutig je Zustellung, gleich bei Wiederholungen
X-CaptchaCore-Timestamp: 1758196800
X-CaptchaCore-Signature: sha256=3b2a…

{
  "event": "usage.limit_reached",
  "occurred_at": "2026-09-18T14:00:00+02:00",
  "organisation_id": 42,
  "data": { "threshold_percent": 100, "used": 10000, "limit": 10000, "plan": "pro", "period": "2026-09" }
}

Verifying the signature

The signature is an HMAC-SHA256 over “timestamp.body” with the secret from the customer area. Compare in constant time and reject timestamps older than five minutes — that prevents replays of intercepted requests.

$secret    = getenv('CAPTCHACORE_WEBHOOK_SECRET');
$body      = file_get_contents('php://input');
$timestamp = $_SERVER['HTTP_X_CAPTCHACORE_TIMESTAMP'] ?? '';
$expected  = 'sha256=' . hash_hmac('sha256', $timestamp . '.' . $body, $secret);

if (abs(time() - (int) $timestamp) > 300
    || ! hash_equals($expected, $_SERVER['HTTP_X_CAPTCHACORE_SIGNATURE'] ?? '')) {
    http_response_code(401); exit;
}

$event = json_decode($body, true);
// … verarbeiten, dann schnell mit 2xx antworten
http_response_code(204);

Delivery and retries

  • Answer with a 2xx status within 5 seconds — process anything expensive asynchronously.
  • On errors or timeout, delivery is retried up to three times (after 30 seconds and after 5 minutes) with the same delivery ID — make your processing idempotent with it.
  • After ten consecutive failures the webhook is switched off; in the customer area you see the reason and can switch it on again.
  • Every delivery with response code, duration and response excerpt is listed under “Deliveries” — also for troubleshooting on your side.

Versioning and stability

Anyone maintaining a plugin needs to know what they can rely on. These are our commitments.

The path version stays stable

Within /api/v2 we neither remove nor rename response fields. New fields may appear at any time — read responses tolerantly and do not fail on unknown keys.

V1 is deprecated

The old verification interface under /api/v1 and the widget captchacore.min.js keep running for older integrations only; the widget shows visitors a notice to update. Please switch to V2 — we will announce the shutdown date in good time.

The widget updates itself

Through the CDN path captchacore-v2.min.js you always get the maintained build. Do not ship your own copy, or you will miss improvements to bot detection.

We announce changes

Anything that could affect existing integrations is announced in advance on the public system status and by email to the technical address on file.

← Back Widget Next → Detection