DOCUMENTATION

Integration Guide

Everything you need to embed CaptchaKit in your signup forms and verify tokens on your server.

Integrate with Claude Code

The fastest way to add CaptchaKit to your app

Copy the prompt below, fill in your site key and API key from your dashboard, and paste it into Claude Code. Claude will find your form, add the widget, and wire up server-side verification — usually in under a minute.

I'm adding CaptchaKit to my app. CaptchaKit replaces the standard CAPTCHA checkbox with a short arcade game — users play for ~10 seconds to prove they're human. It's bot-proof and has zero friction for real users.

How it works:
- A <script> tag loads the widget from https://captchakit.com/widget.js
- A <div data-gamecaptcha data-site-key="..." data-game="connect4"> renders the game inside my form
- When the player passes, a signed token is stored on the div's data-token attribute and a gamecaptcha:pass event fires on document
- I must verify the token server-side before processing the form: POST https://captchakit.com/api/verify with header Authorization: Bearer <api_key> and body { token, siteKey }
- Tokens expire in 10 minutes and can only be used once

My credentials (from my CaptchaKit dashboard at captchakit.com/dashboard):
- Site key: [PASTE YOUR SITE KEY — starts with gc_]
- API key: [PASTE YOUR API KEY — starts with cgv_]
- Game I want to use: connect4

Please:
1. Add the CaptchaKit widget.js script to my page
2. Add the widget div inside my existing form where the CAPTCHA should go
3. Update my form submit handler to read the token and block submission if it's missing
4. Add server-side token verification in my form handler — reject the request if verification fails

Tips for best results

  • • Paste your actual site key and API key — Claude will put them in the right places
  • • Tell Claude which specific form or file to modify if your project has more than one
  • • If Claude asks to install a package, there is no npm package — the widget is a plain script tag
  • • After Claude makes changes, run your dev server and play a game to confirm the token is being sent

Quick Start

The simplest possible integration — two lines of HTML, one verification call.

index.htmlhtml
<!-- Step 1: Add CaptchaKit script to your page (before </body>) -->
<script src="https://captchakit.com/widget.js"></script>

<!-- Step 2: Add the widget inside your form -->
<form id="signup-form">
  <input type="email" name="email" placeholder="Email" />

  <div
    data-gamecaptcha
    data-site-key="gc_connect4_your_key_here"
    data-game="connect4"
    data-theme="dark"
  ></div>
  <!-- data-theme: "dark" (default) or "light" — match your site's background -->

  <button type="submit">Sign up</button>
</form>

<!-- Step 3: Read the token on submit -->
<script>
  document.getElementById('signup-form').addEventListener('submit', async (e) => {
    e.preventDefault();
    const token = e.target.querySelector('[data-gamecaptcha]').dataset.token;
    if (!token) return alert('Please complete the game first.');

    const res = await fetch('/api/signup', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ email: e.target.email.value, gamecaptchaToken: token }),
    });
  });
</script>

IMPORTANT

Always verify the token on your server before processing form submissions. Client-side checks alone are insufficient — a bot could submit directly to your API.

embed.js Script Tag

The embed.js approach works in any HTML page — no bundler, no framework required. Drop a single <script> tag, add the widget div, and listen for the gc:pass event.

index.htmlhtml
<!-- 1. Load the script (once, anywhere on the page) -->
<script src="https://captchakit.com/widget.js" async></script>

<!-- 2. Drop the widget div wherever you want it in your form -->
<form id="my-form">
  <input type="email" name="email" placeholder="Email address" />

  <div data-gamecaptcha data-site-key="gc_xxxxx" data-game="connect4"></div>

  <button type="submit">Sign up</button>
</form>

<!-- 3. Listen for the gamecaptcha:pass event to read the token -->
<script>
  document.addEventListener('gamecaptcha:pass', function (e) {
    var token = e.detail.token;

    // Store it so your submit handler can read it
    document.getElementById('my-form').dataset.gcToken = token;
  });

  document.getElementById('my-form').addEventListener('submit', async function (e) {
    e.preventDefault();
    var token = this.dataset.gcToken;

    if (!token) {
      alert('Please complete the game first.');
      return;
    }

    // Send the token to your server for verification
    await fetch('/api/signup', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        email: this.email.value,
        gcToken: token,
      }),
    });
  });
</script>

DATA ATTRIBUTES

data-site-keyrequired

Your site key from the dashboard (e.g. gc_xxxxx).

data-game

Game ID to show. Defaults to connect4. Options: connect4, whackabot, colormatch, fruitsorter, mazerunner, pacmanlite, stackit.

gc:pass EVENT DETAIL

{ token: string, siteKey: string, game: string }

The event fires on documentand bubbles up from a sentinel element at the widget's original position. Send the token to your server and verify it with POST /api/verify.

How Verification Works

The token flow in 4 steps.

1

Widget loads

The script tag renders the game inside the div. The player's site key is read from the data-site-key attribute.

2

Player passes the game

When the human wins, the widget calls our server with the site key. The server confirms the key is valid and issues a signed JWT.

3

Token written to the element

The signed token is stored in data-token on the widget div. Your form submit handler reads it.

4

You verify server-side

Your server calls POST /api/verify with the token and site key. We return { success: true }. Only then do you process the form.

Token format (JWT, expires in 10 minutes)

{ siteKey: "gc_connect4_xxx", gameId: "connect4", iat: ..., exp: ... }

Server Verification

Call this endpoint from your server — never from the browser.

POST/api/verify

Request headers

Authorization: Bearer YOUR_API_KEYContent-Type: application/json

Request body

{ "token": "...", "siteKey": "gc_..." }

✅ Success response

{ "success": true, "gameId": "connect4" }

❌ Failure response

{ "success": false, "error": "Invalid or expired token" }
// Express example
app.post('/api/signup', async (req, res) => {
  const { email, gamecaptchaToken } = req.body;

  // Verify the token server-side
  const verifyRes = await fetch('https://captchakit.com/api/verify', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer YOUR_API_KEY',  // from your dashboard
    },
    body: JSON.stringify({
      token: gamecaptchaToken,
      siteKey: 'gc_connect4_your_key_here',
    }),
  });

  const { success, gameId } = await verifyRes.json();

  if (!success) {
    return res.status(400).json({ error: 'Bot detected. Please try again.' });
  }

  // ✅ Token is valid — proceed with signup
  await createUser(email);
  res.json({ ok: true });
});

Framework Guides

Complete examples for the most common stacks.

'use client'; // Next.js App Router

import { useRef, useState } from 'react';

export default function SignupForm() {
  const widgetRef = useRef<HTMLDivElement>(null);
  const [loading, setLoading] = useState(false);

  const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    const token = widgetRef.current?.dataset.token;

    if (!token) {
      alert('Please complete the game first!');
      return;
    }

    setLoading(true);
    const res = await fetch('/api/signup', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        email: (e.target as HTMLFormElement).email.value,
        gamecaptchaToken: token,
      }),
    });

    setLoading(false);
    if (res.ok) window.location.href = '/welcome';
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="email" name="email" required />

      {/* The widget script must be loaded in a script tag,
          e.g. in <head> or via next/script with strategy="beforeInteractive" */}
      <div
        ref={widgetRef}
        data-gamecaptcha
        data-site-key="gc_connect4_your_key_here"
        data-game="connect4"
      />

      <button type="submit" disabled={loading}>
        {loading ? 'Signing up...' : 'Sign up'}
      </button>
    </form>
  );
}

Game Comparison

Choose the game that fits your form and audience.

GAMEAVG. TIMEDIFFICULTYBEST FORMOBILE
🔴Connect 415sEasyGeneral signups
🍒Pac-Man Lite8sEasyGaming sites
🔨Whack-a-Bot12sMediumLogin forms
🎨Color Match10sMediumSignup flows
🌀Maze Runner20sHardHigh-security forms
🍎Fruit Sorter15sMediumE-commerce checkouts
🧱Stack It10sEasyComment forms
🫧Tap in Order6sEasyCheckout flows
🔧Pipe Connect12sMediumAccessibility-first sites
🏓Brick Breaker12sMediumGaming & community sites
Penalty Kick8sEasySports & ticketing
🟪Tetromino Drop15sMediumHigh-security forms
Mini Golf Putt12sMediumLeisure & booking sites
🚀Asteroid Dodge8sMediumGaming & tech sites
🎳Skee-Ball12sMediumRetail & arcade brands
🏀Hoops10sMediumSports & ticketing
🐸Frogger Hop12sMediumGeneral signups
🦆Duck Hunt10sEasyLogin forms

Accessibility

WCAG 2.1 requires an alternative for any CAPTCHA. CaptchaKit ships one built in — no extra integration work on your side.

Built-in accessible check

Every widget shows a "Can't play this game?" link that switches to a screen-reader-friendly question. Passing it issues the exact same one-time token, so your server-side verification flow is unchanged.

Keyboard play

All games are fully keyboard-playable (arrows, space, enter, or number keys — each game shows its controls in the footer) and announce progress to screen readers via live regions.

🕹️ Arcade — visitors pick their game

Own more than one game? The widget automatically shows a "Switch game" button that opens a tile view of every game you own — on every platform, since it lives inside the widget itself. Visitors complete whichever they like; your site key, embed code, and server-side verification stay exactly the same. Prefer pinning each form to one game? Turn Arcade off in your dashboard.

Webhooks

Business accounts can receive signed real-time events at their own endpoints — manage them from the dashboard. Events: verification.passed, verification.failed, game.solved.

Signature verification

Each delivery includes an X-CaptchaKit-Signature header: t=<unix-ms>,v1=<hex> where v1 is the HMAC-SHA256 of "<t>.<raw body>"using your endpoint's signing secret (shown once at creation).

Endpoints that fail 20 deliveries in a row are disabled automatically; re-add them from the dashboard when fixed.

API Reference

All endpoints at a glance.

POST/api/verify🔑 Auth required

Verify a game completion token. Tokens are one-time-use: a second verify of the same token returns success: false ("Token already used"). Requires Authorization: Bearer header.

POST/api/game-complete

Called by the embed widget internally. Returns a signed JWT on game pass.

GET/api/auth/me🔑 Auth required

Get the current authenticated user, their API key, and owned games.

GET/api/me/analytics🔑 Auth required

Solve counts, verification totals and pass rate (?days=7|30|90). Daily series and per-game breakdowns on Business.

POST/api/me/api-key🔑 Auth required

Rotate your API key. The old key stops working immediately; the new key is returned once.

GET/api/health

Service health for uptime monitors. 200 when app + database are reachable, 503 otherwise.