Skip to main content
The Android SDK mirrors the browser SDK: configure it on startup, call getSession() when the user performs a sensitive action, and POST the sealed token to your backend for verification.
TripwireClient.configure(
    context,
    TripwireConfiguration(publishableKey = "pk_live_..."),
)

// At action time (signup, login, checkout)
val handoff = TripwireClient.getSession()
The mobile SDKs are currently in private beta. Contact support for access credentials and distribution instructions.

Requirements

  • Android 5.0 (API 21) and up
  • Kotlin 1.9+
  • Android Gradle Plugin 8.2+
  • NDK r26+ (bundled with Android Studio Hedgehog and later)

Install

// settings.gradle.kts
dependencyResolutionManagement {
    repositories {
        mavenLocal()
        google()
        mavenCentral()
    }
}

// app/build.gradle.kts
dependencies {
    implementation("com.tripwirejs:tripwire-android:0.1.0")

    // Optional — adds Play Integrity, Advertising ID, and GSF-ID signals.
    // Only include if your app already ships with Google Play Services.
    implementation("com.tripwirejs:tripwire-android-gms:0.1.0")
}
The only required manifest permission is android.permission.INTERNET. The SDK declares no permissions of its own.

Configure on startup

Call configure() from Application.onCreate() so signal collection is running by the time your first screen mounts.
class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()
        TripwireClient.configure(
            this,
            TripwireConfiguration(publishableKey = "pk_live_your_publishable_key"),
        )
    }
}

TripwireConfiguration

data class TripwireConfiguration(
    val publishableKey: String,
    val enableBehavioralSignals: Boolean = true,
    val enableAntiTamper: Boolean = true,
    val enableHiddenWebView: Boolean = true,
    val enableCloudIdentifier: Boolean = false,
)
FieldDefaultDescription
publishableKeyRequired. Must start with pk_live_ or pk_test_. Secret keys (sk_*) are rejected at configure-time.
enableBehavioralSignalstrueAccelerometer and touch-stroke observers. Disable if your app has strict motion-sensor policies.
enableAntiTampertrueRoot, Magisk, debugger, emulator, and hook detection.
enableHiddenWebViewtrueEnables the attach(webView) handoff so an in-app WebView shares this session.
enableCloudIdentifierfalseOpt-in Google Drive AppData hint. Requires the tripwire-android-gms module. No-op if Play Services are not available.

Get a session at action time

Call getSession() from a coroutine right before the sensitive action — not on app launch.
lifecycleScope.launch {
    val handoff = TripwireClient.getSession()

    val response = httpClient.post("/api/signup") {
        contentType(ContentType.Application.Json)
        setBody(SignupRequest(
            email = email,
            tripwire = TripwireHandoff(handoff.sessionId, handoff.sealedToken),
        ))
    }
}
SessionHandoff is a simple data class:
data class SessionHandoff(val sessionId: String, val sealedToken: String)
  • sessionId is stable across getSession() calls for the life of the client.
  • sealedToken is fresh on every call. Send the most recent one to your backend.
  • The SDK never surfaces verdicts, scores, or visitor IDs to the device — verify on your server.

Behavioral capture

Touch-stroke dynamics are opt-in per screen. Call observeTouches when the view is available — typically from onResume.
override fun onResume() {
    super.onResume()
    TripwireClient.observeTouches(rootView, contextId = "checkout")
}
observeTouches wraps any existing View.OnTouchListener on the view; it does not consume events, so your gesture and scroll handlers keep working unchanged.

WebView correlation

If your app hosts Tripwire-protected web content in a WebView, attach it so the web SDK reuses the native session instead of creating its own.
val webView = WebView(context)
TripwireClient.attach(webView)
webView.loadUrl("https://your-app.example.com/signup")
The native bridge exposes the session handoff as window.__TRIPWIRE_NATIVE__, which the browser SDK picks up when loaded from within the WebView.

API reference

MethodDescription
TripwireClient.configure(context, configuration)Validates the publishable key and starts the runtime. Safe to call once at app start. Calling again reconfigures the client.
suspend TripwireClient.getSession(): SessionHandoffFlushes pending observations and returns a sealed handoff. Every call produces a fresh sealedToken.
suspend TripwireClient.waitForFingerprint()Resolves when the durable visitor fingerprint is ready. Optional — getSession() works without it.
TripwireClient.observeTouches(view, contextId)Attaches a non-consuming touch observer to the view. Call once per screen.
TripwireClient.attach(webView)Shares the current session with a hosted WebView.
TripwireClient.destroy()Stops timers and releases resources. Rarely needed outside tests.

Optional permissions

The SDK itself declares zero permissions. Two signals produce richer entropy if the host app opts in.
PermissionUnlocksPlay Store friction
com.google.android.providers.gsf.permission.READ_GSERVICESGoogle Services Framework IDNone.
android.permission.QUERY_ALL_PACKAGESFull installed-package enumerationRequires justification on new submissions. A scoped <queries> tag gives a partial list without justification.
Neither is required. Missing permissions downgrade the matching fields to empty without affecting the overall verdict.

Error handling

Every SDK-produced error is a subclass of the sealed TripwireError type.
sealed class TripwireError(
    val code: String,
    message: String,
    cause: Throwable? = null,
    val retryable: Boolean = false,
) : Exception(message, cause)
CodeRetryableWhen it happens
config.invalid_publishable_keynopublishableKey is missing, uses the sk_* secret prefix, or doesn’t match pk_live_* / pk_test_*.
client.not_configurednoA client method was called before configure().
session.create_failedvariesSession handshake with the Tripwire API failed. The retryable flag reflects the underlying cause.
session.handshake_expirednoThe server expired the session. Call configure() again to mint a new one.
transport.batch_post_failedvariesA signal batch upload failed.
transport.rate_limitedyesHTTP 429 from the API. Back off and retry.
transport.upgrade_requirednoHTTP 410/426. The SDK version is too old — upgrade the app.
transport.networkyesDNS, TLS, or connectivity failure.
crypto.failurenoA crypto primitive failed. Usually a platform issue worth reporting.
internalnoUnexpected state. File a support ticket with the message and cause.
The code values are stable. Category prefixes (config.*, transport.*) are safe to branch on with a wildcard — new codes within a category keep the same retry semantics.

Fallback policy

If Tripwire fails, your app should still work. Log the error and continue without a handoff; treat the missing server-side signal according to the sensitivity of the action.
suspend fun tripwireHandoff(): SessionHandoff? =
    try {
        TripwireClient.getSession()
    } catch (err: TripwireError) {
        if (err.retryable) {
            runCatching { TripwireClient.getSession() }.getOrNull()
        } else {
            reportError(err)
            null
        }
    }
See Going to production for guidance on fall-open vs. fall-closed policy.

Best practices

  • Configure in Application.onCreate() so signals are collecting before any screen opens.
  • Call getSession() late — right before the sensitive action, inside a coroutine.
  • Wrap errors — degrade gracefully when the SDK can’t produce a handoff.
  • Reuse the singletonTripwireClient is a process-wide singleton. Do not hold per-screen copies.

What’s next

Server verification

Verify the sealed token on your backend

Browser SDK

Equivalent guide for the web surface

iOS SDK

Native iOS integration

Going to production

Rollout checklist and monitoring