Docs

Add Perk in three steps

Copy the widget into your form, unlock submit when it verifies, then spend the token once.

Step 1

Drop the widget into your form

Load the script once, then place <captcha-widget> where the captcha should appear. Keep the submit button disabled for now.

<script type="module"
  src="https://captcha.vperked.online/static/captchaWidget.js"
  integrity="sha384-…"
  crossorigin="anonymous"></script>

<form method="post" action="/signup">
  <input type="email" name="email" required>
  <captcha-widget
    name="captcha_token"
    endpoint="https://captcha.vperked.online"
    action="signup">
  </captcha-widget>
  <button type="submit" disabled>Create account</button>
</form>
Pin the script with SRI. Copy files.captchaWidget.js from GET /static/integrity.json into integrity after each deploy. Module SRI needs crossorigin="anonymous".
Use the same action everywhere. signup here must match the value you send to /captcha/consume later. Pick a short name per form: login, checkout, contact.

Step 2

Only enable submit after verify

When the user finishes the check, Perk inserts a hidden token and fires captcha-verified. Turn the button on then. If anything fails, turn it back off.

<script type="module">
  const form = document.querySelector('form');
  const button = form.querySelector('button[type="submit"]');
  const widget = form.querySelector('captcha-widget');

  widget.addEventListener('captcha-verified', () => {
    button.disabled = false;
  });
  widget.addEventListener('captcha-error', () => {
    button.disabled = true;
  });
</script>
No token exists until verify succeeds. Do not invent a placeholder field. The widget writes the hidden input only after a real pass.

Step 3

Spend the token once, then continue

Before you create a session or write to your database, spend the token from the browser so the client IP matches verify. Use consumeToken() from the widget module—it sends X-Perk-Client: widget, CORS, and the Sec-Fetch headers Perk expects (including on cross-origin pages).

<script type="module">
  import { consumeToken } from 'https://captcha.vperked.online/static/captchaWidget.js';

  form.addEventListener('submit', async (event) => {
    event.preventDefault();
    const token = form.elements.captcha_token.value;
    if (!token) return;

    const res = await consumeToken(token, 'signup');
    const data = await res.json();
    if (!res.ok || data.status !== 'success') {
      throw new Error(data.status || 'invalid request');
    }
    // token spent — continue signup
  });
</script>

Without your own module script, load the widget with <script type="module" src="https://captcha.vperked.online/static/captchaWidget.js"></script>, then call await window.PerkCaptcha.consumeToken(token, 'signup'). On another origin, configure the endpoint first:

window.PerkCaptcha.configure({
  endpoint: 'https://captcha.vperked.online',
  action: 'signup',
});
await window.PerkCaptcha.consumeToken(token);
ResponseWhat you do
success Token spent. Continue your signup / login / checkout.
already used Replay. Reject. Ask them to verify again.
expired Too old. Reject and re-verify.
rate limited Too many consume attempts. Wait, then verify again.
invalid request Missing token, wrong action, or bad signature. Reject.
bot detected Request did not look like the widget (missing X-Perk-Client or browser fetch headers). Use consumeToken() or fix your fetch.
Same IP as verify. Browser consume is the simple path (see the sign up example). Do not hand-roll fetch('/captcha/consume') without X-Perk-Client: widget. Server-to-server consume is only for origins in PERK_TRUSTED_SITES: send X-Perk-Site, the end-user IP as X-Perk-Client-IP (or JSON client_ip), and a dashboard API key or programmatic secret. Challenge and verify stay widget-only.

Raw HTTP (trusted site backend). Browser-shaped traffic should use consumeToken() instead.

POST https://captcha.vperked.online/captcha/consume
Content-Type: application/json
X-Perk-Site: https://shop.example.com
X-Perk-Key: perk_…
X-Perk-Client-IP: 203.0.113.40

{ "captcha_token": "ok:ct_…", "action": "signup" }
Cross-origin, JavaScript API, attributes, events, theming

Cross-origin pages

If your page is not served from captcha.vperked.online, set endpoint on the widget (or a meta tag). Verify and consume call that host with CORS. Any http(s) site can embed; you do not need an origin allowlist.

<meta name="perk-endpoint" content="https://captcha.vperked.online">
<captcha-widget name="captcha_token" action="checkout"></captcha-widget>
import { configure, consumeToken } from 'https://captcha.vperked.online/static/captchaWidget.js';

configure({ endpoint: 'https://captcha.vperked.online', action: 'checkout' });
const res = await consumeToken(token); // action from configure()

Privacy browsers (Brave, Firefox strict) may block third-party cookies. The widget resends the session id from the challenge response in the verify body—no extra wiring needed.

JavaScript API

ExportPurpose
configure({ endpoint, action, debug }) Defaults for fetch helpers. HTML attributes on <captcha-widget> still win.
consumeToken(token, action?) Spend a token from the browser with the headers Perk expects.
window.PerkCaptcha Same helpers without import: CaptchaWidget, configure, consumeToken.

Attributes

AttributeDefaultMeaning
name captcha_token Hidden input name after verify.
endpoint script origin Perk host when the page is on another site.
action default Binds the token. Must match consume.
scheme inherit Follows the page. Force dark or light.
notify off Optional error toasts. Success stays in the widget only.
allow-frame off Opt in to running inside an iframe. Default rejects framed embeds.

Closed shadow root (::part still works). The hidden token is in light DOM for forms — host XSS can read it; consume immediately. Browser evaluate() is advisory; the server decides.

Events

EventWhen
captcha-verified Token ready. detail.captcha_token matches the hidden field.
captcha-error Failed; the widget locks until the page is refreshed. Use detail.body for copy.

Theming

Optional CSS variables. Live skins: customize.

captcha-widget {
  --perk-accent: #fff;
  --perk-bg: #000;
  --perk-fg: #fff;
  --perk-radius: 14px;
}