Integrate the Caputchin widget
Five-minute integration for a customer who already has a site key. For deeper reference see widget and the api.
1. Add the script
<script src="https://cdn.jsdelivr.net/npm/@caputchin/widget@1/dist/widget.js" async defer></script>
The widget is published on npm as @caputchin/widget and mirrored on jsDelivr — see ADR-0009. Pin a more specific version (@1.2.3) if you want to opt out of automatic minor / patch upgrades.
2. Place the widget in your form
Marketplace game (widget resolves the ID via the platform's index):
<form action="/contact" method="POST">
<input name="email">
<caputchin-widget sitekey="cpt_pub_..." game="@cooperative-games/bubble-pop"></caputchin-widget>
<button>Submit</button>
</form>
Pool of marketplace games (random per session):
<caputchin-widget
sitekey="cpt_pub_..."
games="@cooperative-games/bubble-pop,@cooperative-games/memory-match,@author/slider">
</caputchin-widget>
Your own game, hosted alongside your other static assets:
<caputchin-widget
sitekey="cpt_pub_..."
game="our-game"
game-src="/assets/games/our-game.js">
</caputchin-widget>
You drop one JS file (a single self-contained bundle of your game) anywhere you serve static assets and pass its URL. No bundler plugin, no hosting service required — your existing static-asset directory works.
The widget injects a hidden caputchin-token field on completion. Game code runs inside a sandboxed iframe and cannot reach your page's cookies, storage, DOM, or other form fields — see widget isolation and ADR-0015. Your own page's CSP is unaffected by what runs inside the sandbox.
See widget for all attributes.
3. Verify on your backend
Post the token to /siteverify with your secret. Top level is reCAPTCHA-shape-compatible. See the verify-server-side guide for details.
4. Optional: listen for events
const widget = document.querySelector('caputchin-widget');
widget.addEventListener('start', (e) => { /* e.detail.gameId */ });
widget.addEventListener('complete', (e) => { /* e.detail.token, score, durationMs */ });
widget.addEventListener('nickname', (e) => { /* e.detail.nickname */ });
widget.addEventListener('error', (e) => { /* e.detail.code, message */ });
Four events, by design — see widget — events and principles.
5. Whitelist your domain
In the dashboard, add your origin to the site key's allowed domains list before going live.
Modes
The default is "Cap fires on widget mount, no game UI, score: null". For other behaviors set mode:
<!-- invisible default — Cap on mount, no game -->
<caputchin-widget sitekey="cpt_pub_..."></caputchin-widget>
<!-- Cap fires when the form is submitted instead of on mount -->
<caputchin-widget sitekey="cpt_pub_..." mode="form-submit"></caputchin-widget>
See widget — modes for the full table.
Build your own game on the same host
If you want to render a game (or any custom UX) directly in your app — no sandbox, no iframe, full integration with your own state — use mode="manual". The widget exposes a small programmatic API and your code drives the lifecycle:
<caputchin-widget sitekey="cpt_pub_..." mode="manual" id="cap"></caputchin-widget>
<script>
const w = document.getElementById('cap')
w.addEventListener('complete', e => {
// e.detail.token is the wrapped token; also injected into the enclosing form
})
w.addEventListener('error', e => {
// e.detail.code, e.detail.message
})
// Begin Cap proof-of-work
w.start()
// ...your own game runs in your app's realm, however you like...
// When the user finishes, hand control back to the widget
w.complete({ score: 0.847, durationMs: 4200 })
</script>
mode="manual" is the deliberate opt-out from sandbox isolation: your game code runs in your own page realm with full access to your app, the same as any other JavaScript you ship. Setting game or game-src together with mode="manual" is a configuration error. See widget — programmatic API and principles — untrusted by default.
Mobile
For iOS / Android apps, embed the widget in a WebView. See the mobile-webview guide.