Operations

Behaviour on outage, key rotation, server examples, domains and content security policy.

Error handling & fail-open

If the CaptchaCore service is unreachable, the behavior can be configured:

Fail-open (default)

Forms are still let through. Availability > security during an outage. Recommended for most sites.

CAPTCHACORE_FAIL_OPEN=true

Fail-closed

Forms are blocked if the service does not respond. Only for highly critical systems.

CAPTCHACORE_FAIL_OPEN=false

All API calls have a 3-second timeout. On timeout, your fail-open/closed configuration takes over.

If the visitor's browser still cannot reach the service after three retries, the widget sends the placeholder cc-unavailable instead of an empty field. Your integration then asks the service as usual: if it is down, its fail-open rule applies, otherwise the request is rejected. With data-fail-open="false" on the script tag, the field stays empty and the form waits.

Keys & rotation

Every site has a key pair: a site key (public, in the browser) and a secret key (secret, server only).

Key formats

cc_pub_6e65b5c4597a688c93f3be6e345ee1a5...  ← 7 + 64 hex = 71 Zeichen
cc_sec_8bb043aa6424a1fdb3e048d2d3232894...  ← 7 + 64 hex = 71 Zeichen

Key rotation

On rotation, the old key is moved to rotated status and remains valid for another 24 hours. This enables rolling deployments with zero downtime.

active — current key, used for new requests
rotated — previous key, valid for another 24h (configurable)
revoked — instantly invalid, no further requests possible

Secret key security

  • The secret key is stored in the database as a SHA-256 hash
  • The plain text is shown only once after creation
  • Cannot be recovered — if lost, generate a new key
  • Never output in logs, API responses or error messages

Backend examples

The verify endpoint can be called from any backend. Here are examples for common languages:

PHP (no framework)

$token = $_POST['captchacore_token'] ?? '';

$ch = curl_init('https://api.captchacore.eu/api/v2/verify');
curl_setopt_array($ch, [
    CURLOPT_POST           => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_TIMEOUT        => 3,
    CURLOPT_HTTPHEADER     => [
        'X-CaptchaCore-Key: cc_sec_IHR_SECRET',
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'token'     => $token,
        'form_type' => 'contact',
    ]),
]);
$result = json_decode(curl_exec($ch), true);

if (!$result['valid']) {
    die('Bot erkannt.');
}

Node.js

const token = req.body.captchacore_token;

const res = await fetch('https://api.captchacore.eu/api/v2/verify', {
  method: 'POST',
  headers: {
    'X-CaptchaCore-Key': process.env.CAPTCHACORE_SECRET_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ token, form_type: 'register' }),
});
const { valid, risk_score, action } = await res.json();

if (!valid) return res.status(403).json({ error: 'Bot detected' });

Python

import requests

result = requests.post(
    'https://api.captchacore.eu/api/v2/verify',
    headers={'X-CaptchaCore-Key': 'cc_sec_IHR_SECRET'},
    json={'token': token, 'form_type': 'login'},
    timeout=3,
).json()

if not result['valid']:
    raise Exception('CaptchaCore verification failed')

Infrastructure & domains

Domain architecture

Domain Purpose Type
api.captchacore.euChallenge, verify, status, versionAPI (JSON)
src-eu.captchacore.euWidget JS + Worker (EU-only CDN)CDN (default)
src.captchacore.euWidget JS + Worker (global CDN)CDN (optional)
captchacore.euWebsite, admin panel, docsFrontend

Content Security Policy (CSP)

If you use a Content Security Policy on your website, the following domains must be allowed:

# Minimal (EU-only CDN + API)
script-src  https://src-eu.captchacore.eu;
connect-src https://api.captchacore.eu https://src-eu.captchacore.eu;
worker-src  blob:;

# Mit globalem CDN (zusätzlich)
script-src  https://src.captchacore.eu;
connect-src https://src.captchacore.eu;

Why these entries?

  • script-src — The widget JS (captchacore-v2.min.js) is loaded from the CDN
  • connect-src (API) — The widget calls /api/v2/challenge via fetch()
  • connect-src (CDN) — The PoW worker is loaded from the CDN via fetch()
  • worker-src blob: — The worker is instantiated as a blob URL (avoids CORS)

Strict CSP with a nonce

The widget brings its look as a <style> element. If your CSP does not allow 'unsafe-inline' for styles, give the script tag your page nonce — the widget applies it to its styles automatically:

<script nonce="IHR_NONCE" src="https://src-eu.captchacore.eu/widget/captchacore-v2.min.js"
        data-service-url="https://api.captchacore.eu"
        data-site-key="cc_pub_…" async defer></script>

# passende Richtlinie
script-src     'nonce-IHR_NONCE' https://src-eu.captchacore.eu;
style-src-elem 'self' 'nonce-IHR_NONCE';
connect-src    'self' https://api.captchacore.eu https://src-eu.captchacore.eu;
worker-src     'self' blob:;

The widget uses no inline event handlers and no eval() — 'unsafe-inline' and 'unsafe-eval' are never needed for scripts.

The Symfony bundle and the Laravel package set the nonce via the nonce option; without it, the Laravel package uses the Vite nonce. The nonce must be allowed in script-src and style-src-elem.

CORS headers

CaptchaCore automatically sets the correct CORS headers on all API endpoints. You do not need any CORS configuration on your side. The API responds with:

Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type, Accept, X-CaptchaCore-Key

NGINX example for your website

If you have configured a strict CSP on your website:

# NGINX — CSP für CaptchaCore Widget
add_header Content-Security-Policy
  "script-src 'self' https://src-eu.captchacore.eu;
   connect-src 'self' https://api.captchacore.eu https://src-eu.captchacore.eu;
   worker-src 'self' blob:;"
← Back Detection Next → Help & privacy