Skip to content

Quickstart

Cap is a modern, lightweight, and self-hosted CAPTCHA alternative using SHA-256 proof-of-work and instrumentation challenges.

Unlike traditional CAPTCHAs, Cap is fast and unobtrusive, has no telemetry or tracking, and uses accessible proof-of-work instead of annoying visual puzzles.

We've found that Cap offers a better balance for site admins than big-tech alternatives because it puts the levers of control in your hands, not a third party. You decide the difficulty, you own the data, and you never pay per-request fees.

Cap consists of a client-side widget, which solves challenges and displays the checkbox, and a server-side component, which generates challenges and redeems solutions.

1. Setting up your server

We recommend starting with Cap Standalone. You're gonna need Docker and about 100 MB of free memory (it uses about 50 MB idle). It supports multiple site keys and is compatible with reCAPTCHA's siteverify API, so you can even run it alongside reCAPTCHA and switch over gradually.

Start by creating a docker-compose.yml file:

yaml
version: "3.8"
services:
  cap:
    image: tiago2/cap:latest
    container_name: cap
    ports:
      - "3000:3000"
    environment:
      ADMIN_KEY: your_secret_password
    volumes:
      - cap-data:/usr/src/app/.data
    restart: unless-stopped
volumes:
  cap-data:

Tips

  • ADMIN_KEY is your dashboard login. We recommend making it at least 32 characters.
  • Change 3000:3000 if that port is already in use on your host.
  • If the dashboard is unreachable, try adding network_mode: "host" under the cap service.

Start the container:

bash
docker compose up -d

Open http://localhost:3000 (or your server's IP/domain on port 3000) to access the dashboard. Log in with your admin key, create a site key, and note down both the site key and its secret key - you'll need both.

If you want the best protection and can handle higher memory usage, we also recommend turning on instrumentation challenges and potentially headless browser detection.

2. Adding the widget

You can find example snippets for multiple frameworks on the widget docs. We're gonna assume a basic vanilla implementation here for simplicity.

Add the widget script to your website's HTML:

html
<script src="https://cdn.jsdelivr.net/npm/@cap.js/widget"></script>
<!-- we recommend pinning a version in production -->

Then add the widget component, pointing it at your instance:

html
<cap-widget data-cap-api-endpoint="https://<your-instance>/<site-key>/"></cap-widget>
  • <your-instance> — the public URL of your Cap Standalone instance (e.g. cap.example.com)
  • <site-key> — the site key from your dashboard

Example:

html
<cap-widget data-cap-api-endpoint="https://cap.example.com/d9256640cb53/"></cap-widget>

In your JavaScript, listen for the solve event to capture the token:

js
const widget = document.querySelector("cap-widget");
widget.addEventListener("solve", function (e) {
  const token = e.detail.token;
  // Handle the token as needed
});

Alternatively, you can wrap the widget in a <form></form> and Cap will automatically submit the token alongside other form data as cap-token.

You can also get a token programmatically without displaying the widget by using the programmatic mode.

3. Verifying tokens

Once a user completes the CAPTCHA, your backend must verify the token before trusting it. Send a POST request to your instance's /siteverify endpoint:

sh
curl "https://<your-instance>/<site-key>/siteverify" \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{ "secret": "<key_secret>", "response": "<captcha_token>" }'
js
const { success } = await (
  await fetch("https://<your-instance>/<site-key>/siteverify", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ secret: "<key_secret>", response: "<captcha_token>" }),
  })
).json();
py
import requests
success = requests.post(
  "https://<your-instance>/<site-key>/siteverify",
  json={"secret": "<key_secret>", "response": "<captcha_token>"}
).json().get("success")

print(success)
php
<?php
$data = json_decode(file_get_contents("https://<your-instance>/<site-key>/siteverify",
  false, stream_context_create([
    "http" => [
      "method" => "POST",
      "header" => "Content-Type: application/json",
      "content" => json_encode(["secret"=>"<key_secret>","response"=>"<captcha_token>"])
    ]
  ])
), true);
var_dump($data['success'] ?? false);
  • <key_secret> — the secret key from your dashboard (not the dashboard admin key)
  • <captcha_token> — the token generated by the widget

A successful verification returns:

json
{ "success": true }

That's it! Cap is fully set up. Your users solve challenges client-side, your server verifies tokens, and you own all the data.

Next steps

You're mostly done. If you'd like, you can:

Built in Europe 🇪🇺
Released under the Apache 2.0 License.