Skip to content

Server-Side Verification

Every EU CAPTCHA token generated by the widget must be verified on your server before you trust the form submission. Client-side validation alone is insufficient — a bot can bypass it by submitting a form request directly to your endpoint.

How verification works

  1. The visitor's browser runs the challenge via verify.js and generates a token.
  2. The widget injects a hidden <input name="eu-captcha-response"> into the form. When the user submits, this token reaches your server as part of the POST data.
  3. Your server collects the token, the visitor's IP address, and their User-Agent, then sends all of them to the EU CAPTCHA verification API.
  4. The API returns success: true or false, and a train field that must also be checked.
  5. Your server accepts or rejects the submission accordingly.

Verification request

POST https://api.eu-captcha.eu/v1/verify
Content-Type: application/json

All five fields are required:

Field Description
sitekey Your public sitekey (from the dashboard)
secret Your secret key (from the dashboard — server-side only)
client_ip The visitor's IP address (IPv4 or IPv6). Use X-Forwarded-For or X-Client-IP to get the real IP when behind a CDN or proxy — do not forward the CDN's own address.
client_token The value of the eu-captcha-response form field. May be an empty string — always submit it regardless.
client_user_agent The visitor's User-Agent request header.

Always submit client_token even when it is an empty string. The service uses all attempts — including incomplete ones — to improve detection accuracy.

Verification response

Normal — token valid:

{ "success": true, "train": false }

Normal — token invalid or already used:

{ "success": false, "train": false }

Bypass — validation was skipped:

{ "success": true, "train": true }

Always check train as well as success

When train is true, the API did not perform real validation and forced success to true. This happens when the sitekey does not exist, the secret is wrong, or the sitekey's protection is disabled. A train: true response in production means your verification is silently broken — check your credentials immediately.

Code examples

Official client libraries are available for Python, PHP, Ruby, and Java. The examples below show raw HTTP calls for reference and for languages without a dedicated library.

Node.js

async function verifyCaptcha({ token, clientIp, userAgent }) {
  const res = await fetch(process.env.EUCAPTCHA_VERIFY_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      sitekey:           process.env.EUCAPTCHA_SITE_KEY,
      secret:            process.env.EUCAPTCHA_SECRET_KEY,
      client_ip:         clientIp,
      client_token:      token,
      client_user_agent: userAgent,
    }),
  });
  const data = await res.json();
  // train: true means validation was bypassed — treat as failure
  return data.success === true && data.train !== true;
}

// In your form handler:
app.post('/contact', async (req, res) => {
  const valid = await verifyCaptcha({
    token:     req.body['eu-captcha-response'] ?? '',
    clientIp:  req.ip,
    userAgent: req.headers['user-agent'] ?? '',
  });
  if (!valid) {
    return res.status(400).json({ error: 'CAPTCHA verification failed' });
  }
  // process form...
});

PHP

An official PHP package (myra-security-gmbh/eu-captcha) is available on Packagist and wraps this API with automatic IP detection and configurable fault-tolerance. See PHP Module for installation and usage.

function verifyCaptcha(string $token, string $clientIp, string $userAgent): bool {
    $payload = json_encode([
        'sitekey'           => $_ENV['EUCAPTCHA_SITE_KEY'],
        'secret'            => $_ENV['EUCAPTCHA_SECRET_KEY'],
        'client_ip'         => $clientIp,
        'client_token'      => $token,
        'client_user_agent' => $userAgent,
    ]);

    $ch = curl_init($_ENV['EUCAPTCHA_VERIFY_URL']);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
    curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);

    $response = json_decode(curl_exec($ch), true);
    curl_close($ch);

    // train: true means validation was bypassed — treat as failure
    return $response['success'] === true && $response['train'] !== true;
}

// In your form handler:
$token     = $_POST['eu-captcha-response'] ?? '';
$clientIp  = $_SERVER['REMOTE_ADDR'];
$userAgent = $_SERVER['HTTP_USER_AGENT'] ?? '';

if (!verifyCaptcha($token, $clientIp, $userAgent)) {
    http_response_code(400);
    echo json_encode(['error' => 'CAPTCHA verification failed']);
    exit;
}

Python

An official Python package (myra-eucaptcha) is available on PyPI and wraps this API with async support, configurable timeouts, and fault-tolerance defaults. See Python Module for installation and usage.

import os
import requests

def verify_captcha(token: str, client_ip: str, user_agent: str) -> bool:
    response = requests.post(
        os.environ['EUCAPTCHA_VERIFY_URL'],
        json={
            'sitekey':           os.environ['EUCAPTCHA_SITE_KEY'],
            'secret':            os.environ['EUCAPTCHA_SECRET_KEY'],
            'client_ip':         client_ip,
            'client_token':      token,
            'client_user_agent': user_agent,
        }
    )
    data = response.json()
    # train: true means validation was bypassed — treat as failure
    return data.get('success') is True and data.get('train') is not True

# Flask example:
@app.route('/contact', methods=['POST'])
def contact():
    token     = request.form.get('eu-captcha-response', '')
    client_ip = request.remote_addr
    user_agent = request.headers.get('User-Agent', '')

    if not verify_captcha(token, client_ip, user_agent):
        return jsonify({'error': 'CAPTCHA verification failed'}), 400
    # process form...

Platform integrations

If you are using a reverse proxy or infrastructure-level bot protection, the verification described on this page is already handled for you:

Security notes

Security checklist

  • Store EUCAPTCHA_VERIFY_URL, EUCAPTCHA_SITE_KEY, and EUCAPTCHA_SECRET_KEY in environment variables or a secrets manager — never hardcode them or expose them in frontend code.
  • Always verify server-side, even if you validate client-side.
  • If train is true in a production response, your sitekey or secret is misconfigured. Check them in the dashboard immediately.
  • When behind a CDN or load balancer, ensure client_ip is the real visitor IP, not the infrastructure IP — use X-Forwarded-For or X-Client-IP as appropriate.