Embed Caputchin in a mobile app via WebView
Reference for the pattern is mobile. The widget is the same as the web version; only the host shell differs.
1. Open the embed page in a WebView
The platform hosts a mobile-optimized embed page that wraps the widget with mobile-friendly defaults (full viewport, touch-tuned).
URL shape:
https://embed.caputchin.com/?sitekey=cpt_pub_...&game=bubble-pop
Or with a pool:
https://embed.caputchin.com/?sitekey=cpt_pub_...&games=bubble-pop,memory-match
2. Bridge the wrapped token back to native code
The embed page emits the wrapped token via window.postMessage. Your WebView host listens for it.
iOS (WKWebView, 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://embed.caputchin.com/?sitekey=...&game=...")!))
Android (WebView, Kotlin)
class TokenBridge {
@JavascriptInterface
fun onToken(token: String) {
// send `token` to your backend
}
}
webView.settings.javaScriptEnabled = true
webView.addJavascriptInterface(TokenBridge(), "caputchin")
webView.loadUrl("https://embed.caputchin.com/?sitekey=...&game=...")
3. Verify on your backend
Same as web — POST the token to /siteverify with your secret. See the verify-server-side guide.
Receiving widget errors (optional)
The embed page also calls a second native handler — caputchinError — whenever the widget fires its error event. The handler receives a JSON-stringified payload with the same fields as the widget error event detail: { code, message, originalCode? }. The native side may parse it to log, retry, or branch.
Wiring is symmetric to the token handler and entirely optional — apps that don't register caputchinError simply don't receive errors; the embed page already shows a retry UI inside the WebView regardless.
iOS (WKWebView, Swift)
class ErrorHandler: NSObject, WKScriptMessageHandler {
func userContentController(_ controller: WKUserContentController,
didReceive message: WKScriptMessage) {
guard let json = message.body as? String,
let data = json.data(using: .utf8),
let payload = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { return }
// payload["code"], payload["message"], payload["originalCode"] (optional)
}
}
config.userContentController.add(ErrorHandler(), name: "caputchinError")
Android (WebView, Kotlin)
class ErrorBridge {
@JavascriptInterface
fun onError(payloadJson: String) {
val payload = JSONObject(payloadJson)
// payload.getString("code"), payload.getString("message"),
// payload.optString("originalCode", null)
}
}
webView.addJavascriptInterface(ErrorBridge(), "caputchinError")
What you don't need to do
- No native PoW. Cap stays in the WebView. See ADR-0005.
- No mobile-specific game code. Game authors write one
register()call that runs on both platforms. - No paid mobile tier. Mobile is free across all tiers — see mobile.
When native SDKs ship later
A future thin native wrapper (Swift / Kotlin) will encapsulate this WebView setup. It will be free, since it's a DX upgrade over the same WebView. Build slot earned when customer demand emerges — see roadmap.