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#
| Platform | Package | Min Version |
|---|---|---|
| Android (Kotlin) | com.captchacat:android | Android 5.0 (API 21) |
| iOS (Swift) | Captchacat (SPM) | iOS 14.0 |
| React Native | @captchacat/react-native | React Native 0.70 |
Android#
Installation
dependencies {
implementation("com.captchacat:android:1.0.0")
}XML Layout
<com.captchacat.CaptchacatView
android:id="@+id/captcha"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:siteKey="your-site-key" />Kotlin Usage
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
@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
| Property | Type | Required | Description |
|---|---|---|---|
siteKey | String | Yes | Your site's public key |
baseUrl | String | No | Challenge backend URL (default: https://challenge.captchacat.com) |
language | String? | No | Override widget language (e.g., "de", "fr") |
listener | CaptchacatListener? | No | Callback interface for events |
| Callback | Parameters | Description |
|---|---|---|
onSuccess | token: String | Challenge solved, token ready for validation |
onError | error: CaptchacatError | Widget failed to load or encountered an error |
onExpired | — | Token expired, user must re-solve |
| Method | Description |
|---|---|
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.gitAdd via Xcode: File → Add Package Dependencies → paste the URL above.
CocoaPods:
pod 'Captchacat', '~> 1.0'SwiftUI Usage
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
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
| Property | Type | Required | Description |
|---|---|---|---|
siteKey | String | Yes | Your site's public key |
baseURL | String | No | Challenge backend URL |
language | String? | No | Override widget language |
delegate | CaptchacatDelegate? | No | Delegate for events |
| Delegate Method | Parameters | Description |
|---|---|---|
captchacatDidSucceed(_:token:) | view, token: String | Challenge solved |
captchacatDidFail(_:error:) | view, error: CaptchacatError | Load or runtime error |
captchacatDidExpire(_:) | view | Token expired |
| Method | Description |
|---|---|
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-webviewreact-native-webview is a peer dependency required for rendering the widget.
Usage
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
| Prop | Type | Required | Description |
|---|---|---|---|
siteKey | string | Yes | Your site's public key |
baseUrl | string | No | Challenge backend URL |
language | string | No | Override widget language |
onVerify | (token: string) => void | No | Called when challenge is solved |
onError | (error: string) => void | No | Called on load or runtime error |
onExpired | () => void | No | Called when token expires |
style | ViewStyle | No | Container 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 valid400 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:
| Mode | Mobile Behavior |
|---|---|
click_to_solve | User taps the checkbox to start the challenge |
auto_solve | Challenge starts automatically when the widget loads |
invisible | Widget is hidden; challenge runs in the background |
interactive | User 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:
INTERNETin manifest; iOS: no extra config needed) - Check that the challenge backend URL is reachable from the device
Token not received
- Confirm the
onSuccess/onVerifycallback is set - Check that the widget mode isn't
invisiblewithout 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 theCaptchacatView