Skip to content

Android SDK

Android SDK for Android 5.0+ (API 21+), packaged as a single .aar. Compose- and View-based apps both work; the SDK is UI-framework-agnostic.

Demo on GitHub

📦

Captcha-La/android-demo — full runnable example with every integration step.

Install

The SDK ships as a single .aar you download from the CaptchaLa dashboard. Drop it into your app module's libs/ folder and reference it from Gradle:

groovy
// settings.gradle (or repositories block)
dependencyResolutionManagement {
    repositories {
        mavenCentral()
    }
}

// app/build.gradle
android {
    defaultConfig {
        minSdk 21
    }
}

dependencies {
    implementation 'la.captcha:captchala:1.0.2'   // Maven Central
}

Or download the AAR for manual integration:

groovy
// app/build.gradle (drop captchala.aar into app/libs/)
android {
    defaultConfig {
        minSdk 21
        ndk {
            abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
        }
    }
}

dependencies {
    implementation files('libs/captchala.aar')
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3'
    implementation 'androidx.appcompat:appcompat:1.6.1'
    implementation 'com.google.code.gson:gson:2.10.1'
    implementation 'org.bouncycastle:bcprov-jdk18on:1.77'
}

Download: dash.captcha.la/downloads (latest AAR).

xml
<!-- AndroidManifest.xml -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

Build and install the demo with:

bash
./gradlew installDebug

Quick start

kotlin
import la.captcha.sdk.CaptchalaClient
import la.captcha.sdk.CaptchalaConfig
import la.captcha.sdk.CaptchalaError
import la.captcha.sdk.CaptchalaListener
import la.captcha.sdk.CaptchalaResult

class LoginActivity : ComponentActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // 1. Fetch a one-shot server_token from YOUR backend
        //    (which calls the CaptchaLa server API with X-App-Key + X-App-Secret).
        val serverToken = runBlocking { fetchServerTokenFromYourBackend() }

        // 2. Build config — destroy any prior client so a fresh state is built.
        CaptchalaClient.destroy()
        val client = CaptchalaClient.getClient(applicationContext).init(
            CaptchalaConfig.Builder()
                .appKey("YOUR_APP_KEY")
                .action("login")            // login, register, pay, …
                .lang("en")                 // en, zh-CN, zh-TW, ja, ko, ms, vi, id
                .theme("light")             // "light" | "dark"
                .enableVoice(true)
                .enableOfflineMode(true)
                .serverToken(serverToken)
                .onServerTokenExpired { fetchServerTokenFromYourBackend() }
                .build()
        )

        // 3. Listen for terminal events.
        client.setListener(object : CaptchalaListener {
            override fun onReady() { /* challenge UI ready */ }
            override fun onSuccess(result: CaptchalaResult) {
                // Send result.passToken to your backend for validation.
                sendToBackend(result.passToken)
            }
            override fun onFail(error: CaptchalaError)  { /* recoverable */ }
            override fun onError(error: CaptchalaError) { /* terminal */ }
            override fun onClose() { /* user dismissed */ }
        })

        // 4. Open the CAPTCHA from a button tap.
        findViewById<Button>(R.id.btnVerify).setOnClickListener {
            client.verify(this)
        }
    }
}

API surface

SymbolPurpose
CaptchalaClient.getClient(ctx)Singleton entry point. Returns the shared client bound to the app context.
CaptchalaConfig.Builder()Fluent builder for appKey, action, lang, theme, enableVoice, enableOfflineMode, serverToken.
init(config)Initialize the client with a built CaptchalaConfig. Cheap to call — destroys and re-builds state if invoked again.
setListener(listener)Register a CaptchalaListener for onReady, onSuccess, onFail, onError, onClose callbacks.
verify(activity)Open the CAPTCHA on top of the given Activity. Result is delivered through the listener.
CaptchalaResultHolds passToken, challengeId, ttl, isOffline, isClientOnly. Send passToken to your backend.
CaptchalaClient.destroy()Tear down the singleton (release WebView, native handles). Call from Activity.onDestroy if you re-init often.

Server-side validation

Forward result.passToken (or result.token) to your backend and validate it against the CaptchaLa API. Never expose X-App-Secret in client code.

bash
POST https://apiv1.captcha.la/v1/validate
X-App-Key: YOUR_APP_KEY
X-App-Secret: YOUR_APP_SECRET
Content-Type: application/json

{ "pass_token": "<result.passToken>", "client_ip": "<end-user IP>" }

See the API Reference for the full validation endpoint and X-App-Key / X-App-Secret flow.

Troubleshooting

  • UnsatisfiedLinkError at startup
    Make sure your abiFilters covers armeabi-v7a, arm64-v8a, x86, x86_64 so all device shapes find a matching native lib. The demo's app/build.gradle shows the exact filter list.

  • ProGuard / R8 strips SDK classes in release
    Add a keep rule for the SDK package, e.g. -keep class la.captcha.sdk.** { *; } and -dontwarn la.captcha.sdk.** in your proguard-rules.pro.

  • minSdkVersion too low
    The SDK requires minSdkVersion 21 (Android 5.0). Lower targets will fail at compile time.

  • Cleartext HTTP traffic blocked
    Your manifest should have android:usesCleartextTraffic="false" (the demo does). The CaptchaLa endpoint is HTTPS-only.

Requirements

  • Android 5.0+ (minSdkVersion 21)
  • AndroidX (compileSdk 33+)
  • Kotlin 1.8+ or equivalent Java toolchain (Java 17 source / target)
  • ABIs: armeabi-v7a, arm64-v8a, x86, x86_64

MIT-licensed examples · CaptchaLa is operated independently