Widget methods and events
The Caputchin widget ships two custom elements, and your page interacts with them in two directions: you call methods on the element to drive it, and you listen for events it emits to react. This page is the exhaustive reference for both surfaces.
Every event is a CustomEvent dispatched with bubbles: true and composed: true, so it escapes the widget's shadow DOM and you can listen for it directly on the element (or on any ancestor). The data is always on event.detail.
The two elements at a glance
| Element | Purpose | Methods |
|---|---|---|
<caputchin-widget> | Verification only (proof-of-work / checkbox). | start() |
<caputchin-game> | Game host, with optional verification. | pass(), fail() |
The checkbox widget has only start(); it does not expose pass() / fail(). The game host has no start() (its round becomes live on mount for inline, or on the first dialog open for modal / fullscreen). Both elements emit start, pass, error, and degraded; the game host additionally emits dialog-shown and dialog-hidden. Coverage is in the events table below.
Methods
start() (<caputchin-widget> only)
Begins verification immediately. Its purpose is the manual trigger: when the element has trigger="manual", you call start() to fire the proof-of-work solve from your own gesture (for example, on your form's submit) instead of the built-in checkbox. With the default auto / click triggers the element wires its own activation, so you do not call this.
const widget = document.querySelector("caputchin-widget");
widget.start();The game host has no start(). See manual mode for the full driving-it-yourself walkthrough.
pass(payload) (<caputchin-game> only)
Releases the verification gate: the visitor succeeded. Valid only when trigger="manual"; calling it on a non-manual game emits an error with code invalid-call. The first pass() redeems and locks the verdict; later calls are silently ignored (one round per session).
const widget = document.querySelector("caputchin-game");
widget.pass({ trace: roundRecord });payload field | Type | Meaning |
|---|---|---|
trace | string | The opaque round record your game produces. On a gated round the server replays it to derive the authoritative verdict; on an ungated or game-only key it is accepted as-is. Pass an empty string if your interaction has no record to replay. |
There is no score field on this method, the gate is the server's replay of the trace, not a client-claimed score. (A score does appear on the pass event, which is the widget reporting back to you, not you to the widget.)
Calling pass() before the round has started (modal / fullscreen, dialog not yet opened) emits an error with code invalid-call. On a game-only key (no sitekey) there is no gate to release, so pass() simply emits the pass event with token: null.
fail(payload?) (<caputchin-game> only)
Aborts the round: a definitive loss. Valid only when trigger="manual" (otherwise invalid-call). Optional, an unfinished round is treated as a non-pass anyway. It aborts the in-flight verification (the proof-of-work and instrumentation checks) and surfaces an error event with code game-error-relayed.
widget.fail({ code: "out-of-moves", message: "no moves left" });payload field | Type | Meaning |
|---|---|---|
code | string (optional) | Your diagnostic code; surfaced as the error event's originalCode. Defaults to game-failed. |
message | string (optional) | Human-readable reason. |
Events
Listen with addEventListener(type, handler). Every payload lives on event.detail.
| Event | Emitted by | event.detail | Fires when |
|---|---|---|---|
start | both | { gameId: string | null } | Verification begins (auto on mount, on activation, or via start()). gameId is set for a game round, null for the plain widget. |
pass | both | { token: string | null, score: number | null, durationMs: number | null } | Verification is released. token is the wrapped token to send to your backend; null on a game-only key (no sitekey). |
error | both | { code, message, severity, originalCode? } | Anything from a benign config warning to a hard failure. See the error event. |
degraded | both | { reason: "timeout" | "network" | "http" | "malformed" } | The widget could not resolve your configured size / skin / locale in time, so it rendered with its bundled defaults. Not an error. See the degraded event. |
dialog-shown | <caputchin-game> | { layout: "modal" | "fullscreen" } | An overlay game dialog opens (programmatic or first click). |
dialog-hidden | <caputchin-game> | { layout: "modal" | "fullscreen" } | An overlay game dialog closes (programmatic, Escape, or backdrop click). |
The pass event and the token
The pass event is your cue that verification succeeded, but it is not the trust decision. The trust decision is your backend confirming detail.token. Always verify the token on your backend; never grant access off the front-end event alone. In a form, the widget also injects the token as a caputchin-token field so a normal form POST carries it. The score and durationMs on this event are client-reported analytics and may be null; never treat them as a trust signal.
The error event
The error event carries a stable code, a human message, a severity, and sometimes an originalCode. Each code has a fixed default severity so you can filter "kept running" warnings from "actually broke" failures without hardcoding a code-to-severity table, just read detail.severity. The full set of codes:
code | severity | Meaning |
|---|---|---|
invalid-config | warn | An attribute or combination the widget rejected; it degrades gracefully and keeps running. |
invalid-call | warn | A method called when it was not valid (e.g. pass() on a non-manual game, or before the round started). |
verification-failed | error | The proof-of-work or instrumentation check (or the token redeem) failed; no token issued. |
game-load-failed | error | The game bundle could not be resolved, loaded, or registered. |
gate-unavailable | error | The server returned an authoritative refusal at bootstrap (a gated key could not supply a valid game). |
game-error-relayed | error | An error surfaced from inside the game (relayed from the iframe, or from a manual fail()). |
detail.originalCode is present when the public code is a generalization of a more specific internal reason (for example, an iframe load failure relayed as game-load-failed carries the raw iframe-load-failed / iframe-script-blocked / game-not-registered in originalCode). Use code for branching and originalCode only for diagnostics.
The degraded event
On mount the widget makes one short call to Caputchin to resolve the size, skin, and locale you configured (and, for a marketplace game, its preferred footprint). If that call fails or is too slow, the widget does not block or break: it renders with the values baked into the bundle and emits degraded so the fallback is never silent. The widget stays fully functional after a degraded event, only the resolved presentation may differ from what you configured (a game may appear at its default size, or the checkbox in its default theme). The widget retries a slow resolve before falling back, so degraded means the retries were exhausted, not that one attempt was slow.
detail.reason tells you why the resolve fell back, so you can surface it in your own telemetry:
reason | Meaning |
|---|---|
timeout | The resolve did not answer within its time budget (service slow or unreachable). |
network | The request itself failed (offline, DNS, blocked, or connection reset). |
http | The service answered with an error status. |
malformed | The service answered, but the body was not valid. |
degraded is a notice, not a failure, so it is deliberately not an error event: a slow resolve should not trip the handlers you wire for real failures. Listen for it only if you want to observe degraded renders, for example to alert when your visitors are seeing fallback presentation.
Listening, in practice
const widget = document.querySelector("caputchin-game");
const onPass = (e) => {
// send e.detail.token to your backend to verify
};
const onError = (e) => {
if (e.detail.severity === "error") {
console.error(e.detail.code, e.detail.message);
}
};
widget.addEventListener("pass", onPass);
widget.addEventListener("error", onError);
// Clean up when you tear down your view:
widget.removeEventListener("pass", onPass);
widget.removeEventListener("error", onError);These are native DOM CustomEvents, so in React you attach them with a ref and addEventListener (not JSX onPass props); the frontend examples show framework-specific wiring. Because every event bubbles and is composed, you can also delegate from a container element rather than binding each widget individually.
See also
- Add the widget: the configuration attributes you set in markup (the input side to this output side).
- Drive verification yourself with manual mode: the tutorial that uses
start()/pass()/fail(). - Verify on your backend: what to do with the
passevent's token. - Frontend examples: wiring these events in React, Vue, and plain JS.