---
type: how-to
---

# Integrate the Caputchin widget

Five-minute integration for a customer who already has a site key. For deeper reference see [widget](../widget.md) and the [api](../api.md).

## 1. Add the script

```html
<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`](../widget.md) and mirrored on jsDelivr — see [ADR-0009](../adr/0009-jsdelivr-over-custom-cdn.md). 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):

```html
<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):

```html
<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:

```html
<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](../widget.md#isolation) and [ADR-0015](../adr/0015-sandbox-game-iframe.md). Your own page's CSP is unaffected by what runs inside the sandbox.

See [widget](../widget.md#element-shape) 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](verify-server-side.md) for details.

## 4. Optional: listen for events

```javascript
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](../widget.md#events) and [principles](../principles.md#smallest-possible-api).

## 5. Whitelist your domain

In the [dashboard](../dashboard.md), 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`:

```html
<!-- 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](../widget.md#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:

```html
<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](../widget.md#programmatic-api-manual-mode) and [principles — untrusted by default](../principles.md#untrusted-by-default).

## Mobile

For iOS / Android apps, embed the widget in a WebView. See the [mobile-webview guide](mobile-webview.md).
