Integrations

Mobile SDKs

Native mobile integration for Android, iOS, and React Native

Native mobile SDKs for integrating Captchacat into Android, iOS, and React Native applications.

Overview#

The mobile SDKs wrap the Captchacat widget inside a native WebView component, providing platform-idiomatic APIs for embedding the CAPTCHA in your app. The widget behaves identically to the web version — same challenge types, same security, same server-side validation.

Available SDKs#

PlatformPackageMin Version
Android (Kotlin)com.captchacat:androidAndroid 5.0 (API 21)
iOS (Swift)Captchacat (SPM)iOS 14.0
React Native@captchacat/react-nativeReact Native 0.70

Android#

Installation

build.gradle.kts
dependencies {
    implementation("com.captchacat:android:1.0.0")
}

XML Layout

activity_login.xml
<com.captchacat.CaptchacatView
    android:id="@+id/captcha"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:siteKey="your-site-key" />

Kotlin Usage

LoginActivity.kt
class LoginActivity : AppCompatActivity(), CaptchacatListener {
    private var captchaToken: String? = null
 
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_login)
 
        val captcha = findViewById<CaptchacatView>(R.id.captcha)
        captcha.listener = this
    }
 
    override fun onSuccess(token: String) {
        captchaToken = token
        submitButton.isEnabled = true
    }
 
    override fun onError(error: CaptchacatError) {
        Toast.makeText(this, "Captcha failed to load", Toast.LENGTH_SHORT).show()
    }
 
    override fun onExpired() {
        captchaToken = null
        submitButton.isEnabled = false
    }
 
    private fun submitForm() {
        // Include captchaToken in your API request
        api.login(email, password, captchaToken!!)
    }
}

Jetpack Compose

LoginScreen.kt
@Composable
fun LoginScreen() {
    var token by remember { mutableStateOf<String?>(null) }
 
    Column {
        // ... form fields
 
        CaptchacatWidget(
            siteKey = "your-site-key",
            onSuccess = { token = it },
            onError = { /* handle error */ }
        )
 
        Button(
            onClick = { submitLogin(token!!) },
            enabled = token != null
        ) {
            Text("Sign In")
        }
    }
}

API Reference

PropertyTypeRequiredDescription
siteKeyStringYesYour site's public key
baseUrlStringNoChallenge backend URL (default: https://challenge.captchacat.com)
languageString?NoOverride widget language (e.g., "de", "fr")
listenerCaptchacatListener?NoCallback interface for events
CallbackParametersDescription
onSuccesstoken: StringChallenge solved, token ready for validation
onErrorerror: CaptchacatErrorWidget failed to load or encountered an error
onExpiredToken expired, user must re-solve
MethodDescription
render()Load or reload the widget
reset()Clear state, request a fresh challenge
getToken()Returns the current token or null

iOS#

Installation

Swift Package Manager:

https://github.com/Captchacat-Integrations/iOS.git

Add via Xcode: File → Add Package Dependencies → paste the URL above.

CocoaPods:

Podfile
pod 'Captchacat', '~> 1.0'

SwiftUI Usage

LoginView.swift
import Captchacat
 
struct LoginView: View {
    @State private var token: String?
 
    var body: some View {
        Form {
            TextField("Email", text: $email)
            SecureField("Password", text: $password)
 
            CaptchacatWidget(siteKey: "your-site-key") { token in
                self.token = token
            }
            .frame(height: 60)
 
            Button("Sign In") {
                submitLogin(token: token!)
            }
            .disabled(token == nil)
        }
    }
}

UIKit Usage

LoginViewController.swift
import Captchacat
 
class LoginViewController: UIViewController, CaptchacatDelegate {
    private var captchaToken: String?
 
    override func viewDidLoad() {
        super.viewDidLoad()
 
        let captcha = CaptchacatView()
        captcha.siteKey = "your-site-key"
        captcha.delegate = self
        captcha.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(captcha)
 
        NSLayoutConstraint.activate([
            captcha.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16),
            captcha.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16),
            captcha.heightAnchor.constraint(equalToConstant: 60)
        ])
    }
 
    func captchacatDidSucceed(_ view: CaptchacatView, token: String) {
        captchaToken = token
        submitButton.isEnabled = true
    }
 
    func captchacatDidFail(_ view: CaptchacatView, error: CaptchacatError) {
        showAlert("Captcha failed to load")
    }
 
    func captchacatDidExpire(_ view: CaptchacatView) {
        captchaToken = nil
        submitButton.isEnabled = false
    }
}

API Reference

PropertyTypeRequiredDescription
siteKeyStringYesYour site's public key
baseURLStringNoChallenge backend URL
languageString?NoOverride widget language
delegateCaptchacatDelegate?NoDelegate for events
Delegate MethodParametersDescription
captchacatDidSucceed(_:token:)view, token: StringChallenge solved
captchacatDidFail(_:error:)view, error: CaptchacatErrorLoad or runtime error
captchacatDidExpire(_:)viewToken expired
MethodDescription
render()Load or reload the widget
reset()Clear state, request a fresh challenge
getToken()Returns the current token or nil

React Native#

Installation

npm install @captchacat/react-native react-native-webview

react-native-webview is a peer dependency required for rendering the widget.

Usage

LoginScreen.tsx
import { Captchacat } from '@captchacat/react-native';
import { useState } from 'react';
import { View, TextInput, Button, Alert } from 'react-native';
 
function LoginScreen() {
    const [token, setToken] = useState<string | null>(null);
 
    return (
        <View style={{ padding: 16 }}>
            <TextInput placeholder="Email" />
            <TextInput placeholder="Password" secureTextEntry />
 
            <Captchacat
                siteKey="your-site-key"
                onVerify={(t) => setToken(t)}
                onError={(e) => Alert.alert('Error', e)}
            />
 
            <Button
                title="Sign In"
                disabled={!token}
                onPress={() => submitLogin(token!)}
            />
        </View>
    );
}

Props

PropTypeRequiredDescription
siteKeystringYesYour site's public key
baseUrlstringNoChallenge backend URL
languagestringNoOverride widget language
onVerify(token: string) => voidNoCalled when challenge is solved
onError(error: string) => voidNoCalled on load or runtime error
onExpired() => voidNoCalled when token expires
styleViewStyleNoContainer style overrides

Server-Side Validation#

Token validation works identically to the web integration. After receiving the token from the mobile SDK, send it to your backend, then validate it with the Captchacat API:

POST https://challenge.captchacat.com/validate_token
 
{
  "api_key": "your-secret-api-key",
  "token": "token-from-mobile-sdk"
}
  • 200 OK — token is valid
  • 400 Bad Request — token is invalid or expired

See Server-Side Validation for implementation examples in all supported languages.


Widget Modes#

All widget modes configured in your site settings work in the mobile SDKs:

ModeMobile Behavior
click_to_solveUser taps the checkbox to start the challenge
auto_solveChallenge starts automatically when the widget loads
invisibleWidget is hidden; challenge runs in the background
interactiveUser taps checkbox, then traces the Bezier curve via touch

Troubleshooting#

Widget doesn't load

  • Verify your site key is correct and the site is active in the dashboard
  • Ensure your app has internet permission (Android: INTERNET in manifest; iOS: no extra config needed)
  • Check that the challenge backend URL is reachable from the device

Token not received

  • Confirm the onSuccess / onVerify callback is set
  • Check that the widget mode isn't invisible without a trigger mechanism
  • Verify the domain configured in the dashboard matches your setup

Interactive challenge not responding to touch

  • Ensure the WebView has focus and isn't blocked by overlapping views
  • On Android, verify setClickable(true) is set on the CaptchacatView

Built with precision. Designed for developers.