---
type: how-to
title: Mobile apps integration
summary: Embed the widget in an iOS, Android, React Native, or browser-iframe WebView, and bridge the verified token back to your code.
---

Caputchin runs inside a native app through a hosted embed page. You point a WebView at that page, the visitor clears the challenge inside it, and the page bridges the resulting token (and any error) back to your native code. The verification itself is identical to the web; only the host shell differs.

## 1. Open the embed page

Point a WebView at `https://caputchin.com/embed` with at least a site key:

```
https://caputchin.com/embed?sitekey=cpt_pub_...&game=caputchin/games/leaf-memory
```

Omit the game parameters for the plain checkbox. A `games=a,b` list picks one at random per session.

<Callout type="warn">
If the site key has the game gate turned on (its **Require a game to verify** setting), the plain checkbox cannot pass: the embed must run a game on a gated key. Pass a `game` (or `games`) parameter; the bare checkbox embed, with no game parameters, raises a `gate-unavailable` error there. This is by design, because the gate is enforced on Caputchin's side. See [the game gate](/docs/site-keys/games).
</Callout>

### Query parameters

| Parameter | Required | Effect |
|---|---|---|
| `sitekey` | yes | Your public key (`cpt_pub_...`). Missing renders an error shell with no widget. |
| `game` | no | A single marketplace game id (`owner/repo`, or `owner/repo/leaf`). |
| `games` | no | A comma-separated list; one is chosen at random per session. |
| `game-src` | no | URL of your own hosted game bundle, for a self-hosted game. |
| `layout` | no | `inline`, `modal`, `fullscreen`, or `auto`. Defaults to `inline` on the embed. |
| `overlay-width` / `overlay-height` | no | In `modal` / `fullscreen`, size the game dialog: `full`, a pixel number, or `auto` (the game's own preferred size). See [Sizing and layout](/docs/references/widget-sizing). |
| `locale` / `skin` / `config` | no | Forwarded to the widget to pick a language, skin, or configuration. |
| `no-verify` | no | Runs the game with no verification gate (game only, no token). |
| `postMessageTarget` | no | Browser-iframe embedders only (see below). Ignored in a native WebView. |

Omit all three of `game` / `games` / `game-src` for the plain checkbox. There is no `mode` parameter; the widget drives the trigger itself.

## 2. Bridge the token to your code

When the visitor passes, the embed page pushes the token through the channel that matches your host. Register the matching handler.

### iOS (WKWebView, Swift)

The token arrives as a raw string on a message handler named `caputchin`:

```swift
class TokenHandler: NSObject, WKScriptMessageHandler {
  func userContentController(_ controller: WKUserContentController,
                             didReceive message: WKScriptMessage) {
    guard let token = message.body as? String else { return }
    // send `token` to your backend
  }
}

let config = WKWebViewConfiguration()
config.userContentController.add(TokenHandler(), name: "caputchin")
let webView = WKWebView(frame: .zero, configuration: config)
webView.load(URLRequest(url: URL(string: "https://caputchin.com/embed?sitekey=cpt_pub_...&game=caputchin/games/leaf-memory")!))
```

### Android (WebView, Kotlin)

The token arrives as a raw string on a `@JavascriptInterface` named `caputchin`:

```kotlin
class TokenBridge {
  @JavascriptInterface
  fun onToken(token: String) {
    // send `token` to your backend
  }
}

webView.settings.javaScriptEnabled = true
webView.addJavascriptInterface(TokenBridge(), "caputchin")
webView.loadUrl("https://caputchin.com/embed?sitekey=cpt_pub_...&game=caputchin/games/leaf-memory")
```

### React Native (react-native-webview)

The embed posts a JSON string on the single React Native channel. Parse it and branch on `type`:

```jsx
<WebView
  source={{ uri: "https://caputchin.com/embed?sitekey=cpt_pub_...&game=caputchin/games/leaf-memory" }}
  onMessage={(e) => {
    const msg = JSON.parse(e.nativeEvent.data);
    if (msg.type === "caputchin-token") {
      // send msg.token to your backend
    } else if (msg.type === "caputchin-error") {
      // msg.code, msg.message, msg.originalCode (optional)
    }
  }}
/>
```

### Browser iframe (postMessage)

If you embed the page in a browser `<iframe>` rather than a native WebView, the parent relay is **off by default** and fires only when you opt in with an exact target origin via `?postMessageTarget=<origin>` (for example `postMessageTarget=https://app.example.com`). A wildcard (`*`), a path, or anything that does not parse to a bare scheme, host, and port is rejected, so the single-use, site-bound token is never broadcast to an unverified parent. Listen on the parent and check `event.origin` before trusting the message:

```js
window.addEventListener("message", (event) => {
  if (event.origin !== "https://embed-host.example.com") return;
  const msg = event.data;
  if (msg.type === "caputchin-token") {
    // msg.token
  } else if (msg.type === "caputchin-error") {
    // msg.code, msg.message
  }
});
```

## 3. Verify on your backend

This is the same as the web: POST the token to `/siteverify` with your secret. See [Server side integration](/docs/integration-guides/verify-on-your-backend).

## Receiving errors (optional)

Whenever the widget hits an error, the embed page mirrors it through a parallel set of channels: iOS `caputchinError` message handler, Android `caputchinError` interface (`onError(json)`), the React Native channel as `{ type: "caputchin-error", ... }`, and the same gated iframe relay. The payload carries `{ code, message, originalCode? }`. iOS and Android receive it as a JSON string; React Native and the iframe relay receive the typed envelope.

This is entirely optional. An app that registers no error handler simply does not receive errors, and the embed page already shows a retry UI inside the WebView regardless.

## See also

- [Server side integration](/docs/integration-guides/verify-on-your-backend) for the token check.
- [Client side integration](/docs/integration-guides/cdn) for the web equivalent.
