Caputchin
Custom game development

Drive verification yourself with manual mode

By the end of this tutorial you will have driven the Caputchin verification lifecycle from your own code instead of the built-in UI, on whichever of the two elements fits your case. Manual mode (trigger="manual") exists on two different elements, and they do genuinely different jobs:

ElementWhat manual mode gives youYou drive
<caputchin-widget>The built-in checkbox is hidden; you fire the proof-of-work solve from your own trigger (a form submit, a custom button). No game.start() (then the solve usually resolves itself)
<caputchin-game>No iframe; you slot your own game markup into the widget's layout shell and decide the outcome from gameplay.pass() / fail() (no start(); the round is live on mount)

Pick by the question you are answering:

  • "I want the normal invisible/checkbox verification, but triggered by my own gesture and without the default checkbox." → the checkbox widget, below.
  • "I want my own interactive challenge rendered in my own DOM, not an iframe game, and I decide pass/fail." → the game host, below.

Read run your own game for where the game path fits. You need the widget on your page already; if not, do add the widget first.

One limit shared by both: a manual-mode round cannot satisfy a game gate. The checkbox element never gates a key at all (only <caputchin-game> can), and a manual <caputchin-game> round has no replayable trace, so a gated key refuses it. Manual mode is for ungated keys (proof-of-work verification) or game-only use.

Path A: the checkbox widget, your own trigger

Use this when you want the standard verification but want to fire it yourself, for example to run the solve only when the visitor submits a form, or behind your own button instead of the built-in checkbox.

1. Mark the element manual

<caputchin-widget id="cap" sitekey="cpt_pub_..." trigger="manual"></caputchin-widget>
<button id="go" type="button">Verify and continue</button>

With trigger="manual", the built-in checkbox UI is hidden; the element waits for you to start the solve.

2. Start the solve from your gesture

The checkbox element in manual mode exposes a single method, start():

  • start() kicks the proof-of-work solve, replacing the checkbox click. Valid with trigger="manual".

The checkbox widget has no pass() / fail(); once you call start(), the solve resolves on its own and emits the pass event. (The release/abort handles live on the game host, covered in Path B.)

const widget = document.getElementById("cap");

document.getElementById("go").addEventListener("click", () => {
  widget.start(); // begin verification now
});

widget.addEventListener("pass", () => {
  // token issued; submit your form / continue
});

When the solve resolves, the element completes verification and (in a form) injects the caputchin-token field like any other widget. Your backend verifies that token exactly as usual; manual mode changes nothing server-side.

Path B: the game host, your own DOM

Use this when you want to render your own interactive challenge in your own markup, instead of loading an iframe game, and decide the outcome yourself.

1. Mark the element manual and slot your markup

Put trigger="manual" on <caputchin-game> and place your own markup inside it. In every other mode the widget ignores light-DOM children; manual mode is the one mode where it projects them into its layout shell through a <slot>.

<caputchin-game id="cap" sitekey="cpt_pub_..." trigger="manual">
  <!-- Your own markup. The widget renders it inside its shell. -->
  <div id="my-game">
    <p>Tap the banana three times.</p>
    <button id="banana" type="button">🍌</button>
  </div>
</caputchin-game>

With a sitekey present and the key not gated, the widget runs its proof-of-work check in the background; your interaction is the visible part. With no sitekey (or no-verify), it is game-only: your interaction runs with nothing to verify.

2. Drive the outcome from your code

The game host in manual mode exposes pass() and fail(). There is no start(): the round is live once the element mounts (the factory being mounted is the start signal).

  • pass(payload?) releases the verification: the visitor succeeded. Call it once; later calls are ignored (the verdict is locked).
  • fail(payload?) aborts the round. Optional; an unfinished round is treated as a non-pass anyway.

The payloads:

  • pass({ trace }): trace is the opaque round record your interaction produces. On a gated round the server replays it for the verdict; on an ungated or game-only key it is accepted as-is, so pass an empty string (or a simple marker) when there is nothing to replay. There is no client-supplied score here.
  • fail({ code?, message? }): an optional diagnostic code and message, both client metadata, never a trust signal.
const widget = document.getElementById("cap");
let taps = 0;

document.getElementById("banana").addEventListener("click", () => {
  taps += 1;
  if (taps >= 3) {
    widget.pass({ trace: `banana:${taps}` }); // visitor won
  }
});

// Optional: give up explicitly.
// widget.fail({ code: "gave-up", message: "closed the prompt" });

As with the checkbox path, pass() completes verification and injects the token; your backend verifies it the same way.

React to the outcome (both paths)

Listen for the widget's events to update your own UI:

widget.addEventListener("pass", () => {
  // verification released
});
widget.addEventListener("error", (e) => {
  console.warn("verification error", e.detail);
});

The trust decision is always your backend confirming the token, not the event; the event is for UX.

What manual mode cannot do

  • Gate a site key. A key with require a game turned on cannot be satisfied by the checkbox element at all, and refuses a manual <caputchin-game> (it raises a configuration error at setup) because there is no replayable trace. Manual mode is for ungated or game-only keys.
  • Replay a custom game's round. If you need a game that gates, render it as a self-hosted iframe game and produce a trace, not manual mode.

See also

On this page