logo
captchaAPI

5-minute integration

Four steps: create a project, drop in the script, mark your form, verify on the server. If you already have a project, skip to step 2.

1. Create a project and grab your keys

  1. Sign in and go to Projects.
  2. Click New Project, give it a name, and optionally add the domain(s) you'll embed the widget on. Leaving Allowed Domains empty means any origin can use the site key.
  3. On the project detail page you'll now see two keys:
    • Site Key — public. Goes into your HTML.
    • Secret Key — private. Shown once here after creation; goes into your backend config (not your HTML). You can re-reveal it later with password confirmation.

Copy the secret key somewhere durable (password manager, environment variable) before you navigate away. To reveal it again you'll need to enter your account password.

2. Include the script

Add one tag to your page, ideally just before </body>:

<script>window.CAPTCHA_SITE_KEY = 'YOUR_SITE_KEY';</script>
<script src="https://captchaapi.eu/captcha.js" defer></script>

The script auto-initialises every form that carries the data-captcha attribute. Nothing runs until the user interacts with that form.

3. Mark your form

Add data-captcha to the form, and optionally a [data-captcha-status] element to show the protection state to the user.

<form method="POST" action="/contact" data-captcha>
    <input name="email" type="email" required>
    <textarea name="message" required></textarea>

    <span data-captcha-status></span>

    <button type="submit">Send</button>
</form>

When the form submits, the widget inserts a hidden input named captcha_attestation. It contains a signed proof that the PoW was solved. You don't need to add it yourself.

4. Verify the attestation on your server

Put your secret key(s) in an environment variable — never in code or client-side HTML. CAPTCHA_SECRET_KEYS is a comma-separated list so you can deploy a new key alongside the current one during rotation:

CAPTCHA_SECRET_KEYS=your_secret_here
# During rotation, temporarily:
# CAPTCHA_SECRET_KEYS=your_current_secret,your_pending_secret

On the endpoint that receives the form, verify the attestation's HMAC locally. No HTTP call to captchaapi.eu is needed.

<?php

/**
 * @param list<string> $secrets Accept any key in this list.
 */
function verifyCaptcha(string $attestation, array $secrets, string $expectedSiteKey): bool
{
    if (! str_contains($attestation, '.')) return false;

    [$payloadB64, $sigB64] = explode('.', $attestation, 2);

    $actual = base64_decode(strtr($sigB64, '-_', '+/'), strict: true);
    if ($actual === false) return false;

    $matched = false;
    foreach ($secrets as $secret) {
        $expected = hash_hmac('sha256', $payloadB64, $secret, binary: true);
        if (hash_equals($expected, $actual)) {
            $matched = true;
            break;
        }
    }
    if (! $matched) return false;

    $rawPayload = base64_decode(strtr($payloadB64, '-_', '+/'), strict: true);
    if ($rawPayload === false) return false;

    $payload = json_decode($rawPayload, true);
    if (! is_array($payload))                         return false;
    if (($payload['exp'] ?? 0) < time())               return false;
    if (($payload['sk']  ?? '') !== $expectedSiteKey)  return false;

    return true;
}

$secrets = array_filter(array_map('trim', explode(',', getenv('CAPTCHA_SECRET_KEYS') ?: '')));

$ok = verifyCaptcha(
    $_POST['captcha_attestation'] ?? '',
    $secrets,
    'YOUR_SITE_KEY',
);

That's it. Submissions with a missing or invalid attestation fail the check — reject them. See Backend examples for ready-made snippets in Laravel, plain PHP, Node.js, Python, and Go, plus optional single-use replay protection.

Tip: the payload's ol field is true when the project is past its monthly request quota. Verification still succeeds (with the easiest possible difficulty) so your users aren't punished — it's a signal to upgrade.

Next: full API reference · widget configuration · backend examples.