---
type: how-to
title: Integración en apps móviles
summary: Incrusta el widget en un WebView de iOS, Android, React Native o iframe de navegador, y puentea el token verificado de vuelta a tu código.
---

Caputchin corre dentro de una app nativa a través de una página de incrustación alojada. Apuntas un WebView a esa página, el visitante supera el reto dentro de ella, y la página puentea el token resultante (y cualquier error) de vuelta a tu código nativo. La verificación en sí es idéntica a la web; solo difiere el shell anfitrión.

## 1. Abre la página de incrustación

Apunta un WebView a `https://caputchin.com/embed` con al menos una clave de sitio:

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

Omite los parámetros de juego para la checkbox simple. Una lista `games=a,b` elige uno al azar por sesión.

<Callout type="warn">
Si la clave de sitio tiene el gate de juego activado (su ajuste **Exigir un juego para verificar**), la checkbox simple no puede pasar: la incrustación debe correr un juego en una clave con gate. Pasa un parámetro `game` (o `games`); la incrustación de checkbox a secas, sin parámetros de juego, lanza ahí un error `gate-unavailable`. Esto es a propósito, porque el gate se impone del lado de Caputchin. Mira [el gate de juego](/docs/site-keys/games).
</Callout>

### Parámetros de query

| Parámetro | Obligatorio | Efecto |
|---|---|---|
| `sitekey` | sí | Tu clave pública (`cpt_pub_...`). Si falta, renderiza un shell de error sin widget. |
| `game` | no | Un id de juego del marketplace (`owner/repo`, o `owner/repo/leaf`). |
| `games` | no | Una lista separada por comas; uno se elige al azar por sesión. |
| `game-src` | no | URL de tu propio bundle de juego alojado, para un juego autoalojado. |
| `layout` | no | `inline`, `modal`, `fullscreen` o `auto`. Por defecto `inline` en la incrustación. |
| `overlay-width` / `overlay-height` | no | En `modal` / `fullscreen`, dimensiona el diálogo del juego: `full`, un número de píxeles, o `auto` (el tamaño preferido del propio juego). Mira [Tamaño y disposición](/docs/references/widget-sizing). |
| `locale` / `skin` / `config` | no | Se reenvían al widget para elegir idioma, skin o configuración. |
| `no-verify` | no | Corre el juego sin gate de verificación (solo juego, sin token). |
| `postMessageTarget` | no | Solo para incrustadores en iframe de navegador (ver abajo). Se ignora en un WebView nativo. |

Omite los tres `game` / `games` / `game-src` para la checkbox simple. No hay un parámetro `mode`; el widget conduce el disparador por sí mismo.

## 2. Puentea el token a tu código

Cuando el visitante pasa, la página de incrustación empuja el token por el canal que corresponde a tu anfitrión. Registra el handler que corresponda.

### iOS (WKWebView, Swift)

El token llega como una cadena en bruto en un message handler llamado `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)

El token llega como una cadena en bruto en un `@JavascriptInterface` llamado `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)

La incrustación postea una cadena JSON por el único canal de React Native. Parséala y ramifica según `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)
    }
  }}
/>
```

### Iframe de navegador (postMessage)

Si incrustas la página en un `<iframe>` de navegador en lugar de un WebView nativo, el relay al padre está **desactivado por defecto** y se dispara solo cuando lo activas con un origin objetivo exacto vía `?postMessageTarget=<origin>` (por ejemplo `postMessageTarget=https://app.example.com`). Un comodín (`*`), una ruta, o cualquier cosa que no parsee a un esquema, host y puerto a secas se rechaza, así que el token de un solo uso, ligado al sitio, nunca se difunde a un padre no verificado. Escucha en el padre y comprueba `event.origin` antes de fiarte del mensaje:

```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. Verifica en tu backend

Esto es igual que en la web: haz POST del token a `/siteverify` con tu secreto. Mira [Integración del lado del servidor](/docs/integration-guides/verify-on-your-backend).

## Recibir errores (opcional)

Cada vez que el widget topa con un error, la página de incrustación lo refleja por un conjunto paralelo de canales: el message handler `caputchinError` de iOS, la interfaz `caputchinError` de Android (`onError(json)`), el canal de React Native como `{ type: "caputchin-error", ... }`, y el mismo relay de iframe con gate. El payload lleva `{ code, message, originalCode? }`. iOS y Android lo reciben como una cadena JSON; React Native y el relay de iframe reciben el sobre tipado.

Esto es del todo opcional. Una app que no registra ningún handler de error simplemente no recibe errores, y la página de incrustación ya muestra una UI de reintento dentro del WebView de todos modos.

## Véase también

- [Integración del lado del servidor](/docs/integration-guides/verify-on-your-backend) para la comprobación del token.
- [Integración del lado del cliente](/docs/integration-guides/cdn) para el equivalente web.
