Android / Kotlin SDK
Integrate TrustPin into your Android or JVM application for native certificate pinning.
Current version: cloud.trustpin:kotlin-sdk 6.2.0
Platform Requirements
| Platform | Minimum Version |
|---|---|
| Android | API 25+ (full feature support) |
| JVM | Java 11+ |
Kotlin Version: 2.3.0+
Note: The Maven Central artifact is an Android AAR. For server-side JVM, desktop, or Compose Multiplatform targets, request access to the hardened JVM JAR via support@trustpin.cloud.
Installation
Gradle (Kotlin DSL)
Add TrustPin to your build.gradle.kts:
dependencies {
implementation("cloud.trustpin:kotlin-sdk:6.2.0")
}Gradle (Groovy)
Add to your build.gradle:
dependencies {
implementation 'cloud.trustpin:kotlin-sdk:6.2.0'
}Maven
Add to your pom.xml:
<dependency>
<groupId>cloud.trustpin</groupId>
<artifactId>kotlin-sdk</artifactId>
<version>6.2.0</version>
</dependency>Optional Client Adapters — OkHttp / Ktor
Thin, optional integration artifacts published to Maven Central alongside the SDK, versioned in lockstep with it. Each contains only public-API glue, and neither pulls in the SDK or the HTTP client transitively — your app supplies both:
dependencies {
implementation("cloud.trustpin:trustpin-okhttp:6.2.0") // OkHttp
implementation("cloud.trustpin:trustpin-ktor:6.2.0") // Ktor (OkHttp engine); includes trustpin-okhttp
}Quick Start
1. Get Your Credentials
Sign in to the TrustPin Dashboard and retrieve:
- Organization ID
- Project ID
- Public Key (Base64-encoded)
2. Initialize TrustPin
Ship a trustpin.json asset in your app and load it with TrustPinConfiguration.fromAssets(context). Credentials stay out of source.
Place trustpin.json at app/src/main/assets/trustpin.json:
{
"organization_id": "your-org-id",
"project_id": "your-project-id",
"public_key": "your-base64-public-key",
"mode": "strict"
}Build-variant overrides follow standard Android source-set merging — drop a different file under src/debug/assets/trustpin.json or src/staging/assets/trustpin.json to use per-flavor credentials.
| Key | Type | Required | Notes |
|---|---|---|---|
organization_id | String | Yes | Non-empty |
project_id | String | Yes | Non-empty |
public_key | String | Yes | Base64-encoded ECDSA P-256 public key |
mode | String | No | "strict" (default) or "permissive" |
configuration_url | String | No | Must be HTTPS. Overrides the default CDN endpoint |
Then load it during Application.onCreate():
import android.app.Application
import cloud.trustpin.kotlin.sdk.TrustPin
import cloud.trustpin.kotlin.sdk.TrustPinConfiguration
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
CoroutineScope(Dispatchers.IO).launch {
try {
val config = TrustPinConfiguration.fromAssets(this@MyApplication)
TrustPin.setup(config)
TrustPin.awaitConfiguration()
println("TrustPin initialized")
} catch (e: Exception) {
println("TrustPin setup failed: ${e.message}")
}
}
}
}setup() is non-blocking — it starts loading the configuration and returns without waiting. TrustPin.awaitConfiguration() is the fail-closed gate: call it once after setup, immediately before constructing any HTTP client that depends on pinning, to suspend until a validated configuration is loaded (default timeout: 30s) and throw if it didn’t. For synchronous call sites use TrustPin.awaitConfigurationBlocking(), or check TrustPin.isConfigurationLoaded for a non-throwing status read.
Don’t forget to register your Application class in AndroidManifest.xml:
<application
android:name=".MyApplication"
...>
</application>3. Add Network Permission
Ensure your AndroidManifest.xml includes:
<uses-permission android:name="android.permission.INTERNET" />Integration Approaches
| Approach | Best For | Setup Complexity |
|---|---|---|
| OkHttp Integration (Recommended) | Most Android apps | 🟢 Low |
| Retrofit Integration | REST API clients (uses OkHttp under the hood) | 🟢 Low |
| Ktor Client Integration | Ktor-based apps and KMP shared modules | 🟡 Medium |
| Manual Verification | Custom transports, non-OkHttp stacks | 🟠 High |
OkHttp Integration (Recommended)
With the optional trustpin-okhttp adapter , one line replaces the manual SSL wiring and guarantees the factory/trust-manager pair belongs to the same TrustPin instance:
val client = OkHttpClient.Builder()
.trustPin() // or .trustPin(TrustPin.instance("payments"))
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build()// Java
OkHttpClient client = TrustPinOkHttp.trustPin(new OkHttpClient.Builder()).build();Call after TrustPin.setup(...).
Without the adapter, wire the SSL socket factory manually — TrustPinSSLSocketFactory.create() returns an SSL socket factory wired to your TrustPin configuration. Pass it — along with its trust manager — to OkHttpClient.Builder:
import cloud.trustpin.kotlin.sdk.ssl.TrustPinSSLSocketFactory
import okhttp3.OkHttpClient
import java.util.concurrent.TimeUnit
val sslSocketFactory = TrustPinSSLSocketFactory.create()
val httpClient = OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.sslSocketFactory(sslSocketFactory, sslSocketFactory.trustManager())
.build()Retrofit Integration
Retrofit uses OkHttp under the hood — share the same TrustPin-backed client:
class ApiClient {
private val okHttpClient by lazy {
val sslSocketFactory = TrustPinSSLSocketFactory.create()
OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.sslSocketFactory(sslSocketFactory, sslSocketFactory.trustManager())
.build()
}
private val retrofit by lazy {
Retrofit.Builder()
.baseUrl("https://api.example.com/")
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
}Ktor Client Integration
With the optional trustpin-ktor adapter (OkHttp engine only — TLS configuration is engine-specific in Ktor):
import io.ktor.client.*
import io.ktor.client.engine.okhttp.*
val ktorClient = HttpClient(OkHttp) {
engine { trustPin() }
}A Ktor
preconfiguredOkHttpClient bypasses engine configuration — pin it directly with the OkHttp adapter instead.
Without the adapter, plug TrustPin into Ktor’s OkHttp engine manually:
import io.ktor.client.*
import io.ktor.client.engine.okhttp.*
import okhttp3.OkHttpClient
private val httpClient by lazy {
val sslSocketFactory = TrustPinSSLSocketFactory.create()
HttpClient(OkHttp) {
engine {
preconfigured = OkHttpClient.Builder()
.sslSocketFactory(sslSocketFactory, sslSocketFactory.trustManager())
.build()
}
}
}Manual Verification
For custom transports, validate a domain/certificate pair directly with the suspending API:
import java.security.cert.X509Certificate
val certificate: X509Certificate = /* ... */
TrustPin.verify("api.example.com", certificate)Named Instances (Multi-Tenant)
The SDK supports independent named instances via TrustPin.instance(id) for apps that talk to multiple TrustPin projects (for example, separate consumer and admin backends). Because the bundled-asset path loads a single trustpin.json per build, multi-tenant setups currently require the programmatic configuration API — see the upstream Kotlin SDK docs for usage.
Logging
Set the desired verbosity before calling setup() to capture initialization logs:
import cloud.trustpin.kotlin.sdk.TrustPinLogLevel
TrustPin.setLogLevel(TrustPinLogLevel.INFO)Available levels: NONE, ERROR, INFO, DEBUG.
Custom Log Sink
To route SDK log output into your own logging pipeline, install a global TrustPinLogSink. One sink serves all instances and receives every message after per-instance level filtering, tagged with the producing instance id:
TrustPin.setLogSink { level, instanceId, message ->
myLogger.log("[$instanceId] $message")
}
TrustPin.setLogSink(null) // restore the default sinkSinks are called synchronously from SDK internals, including TLS-handshake threads: keep them fast and non-blocking, don’t perform I/O inline, and never call back into TrustPin from a sink.
Monitoring Pin Validation
To feed pin-validation verdicts into your security monitoring — for example, reporting suspected MITM attempts to your backend — install a global TrustPinValidationListener:
import cloud.trustpin.kotlin.sdk.TrustPinValidationListener
import java.security.cert.X509Certificate
TrustPin.setValidationListener(object : TrustPinValidationListener {
override fun onValidationFailure(
instanceId: String,
domain: String,
error: TrustPinError,
presentedCertificate: X509Certificate,
) {
// Fires only for definitive verdicts: PinsMismatch, AllPinsExpired,
// DomainNotRegistered (strict mode). `presentedCertificate` is the
// leaf as received from the network — treat it as untrusted input.
}
override fun onValidationSuccess(instanceId: String, domain: String) {
// Optional — default implementation does nothing.
}
})
TrustPin.setValidationListener(null) // detachThe listener is observe-only: it is invoked strictly after the verdict is decided and cannot veto, approve, or alter a connection. Transient conditions (configuration fetch failures, timeouts) and permissive-mode connections to unregistered domains produce no callbacks. Like log sinks, listeners are called synchronously from TLS-handshake threads — keep them non-blocking and never call back into TrustPin.
Error Handling
TrustPinError is a sealed class with the following variants:
| Variant | Meaning |
|---|---|
DomainNotRegistered | Strict mode and the host isn’t in the configuration |
PinsMismatch | Server certificate doesn’t match any active pin |
AllPinsExpired | Configuration is stale — rotate pins in the dashboard |
InvalidServerCert | Server returned an unparseable certificate |
InvalidProjectConfig | Bad credentials or invalid configuration |
ErrorFetchingPinningInfo | Network failure while loading the configuration |
ConfigurationValidationFailed | JWS signature didn’t verify against the project’s public key |
ConfigIntegrityError | Configuration failed an integrity check — hard stop |
NotInitialized | An API was called before setup() completed successfully |
AlreadyInitialized | setup() was called a second time on the same instance |
SetupInProgress | An operation raced a setup() that hadn’t finished yet |
LockTimeout | An internal lock couldn’t be acquired in time |
Timeout | An operation (e.g. awaitConfiguration) exceeded its timeout |
SSLContextSetupFailed | The pinned SSLContext / socket factory couldn’t be created |
UnsupportedDevice | The runtime environment doesn’t support the required security primitives |
try {
TrustPin.setup(config)
} catch (e: TrustPinError.InvalidProjectConfig) {
// Bad credentials
} catch (e: TrustPinError.ErrorFetchingPinningInfo) {
// Network failure during setup
} catch (e: TrustPinError.NotInitialized) {
// setup() wasn't called, or it failed silently — guard with awaitConfiguration()
} catch (e: TrustPinError) {
// Any other TrustPin failure (UnsupportedDevice, etc.)
}Best Practices
Setup & Initialization
- Initialize in
Application.onCreate()for app-wide coverage. - Use a coroutine scope for async setup — the API is suspend-first (blocking variants are available for synchronous call sites).
- Call
TrustPin.awaitConfiguration()aftersetup(), before constructing any HTTP client that depends on pinning —setup()is non-blocking. - Set the log level before
setup()to capture initialization output. - Handle setup errors gracefully — don’t block app launch.
Security
- Use
TrustPinMode.STRICTin production. - Prefer SPKI pinning; rotate pins in the dashboard before they expire.
- Monitor pin validation failures via
TrustPin.setValidationListener(...)or logging. - Keep credentials outside source control — prefer
TrustPinConfiguration.fromAssets(context)with per-flavortrustpin.jsonfiles, or fetch them at runtime and usewithAndroidStorage(context). - Use HTTPS for all pinned domains.
Performance
- Configuration is cached for 10 minutes with a stale-while-revalidate fallback.
- Reuse
OkHttpClientinstances rather than creating one per request. - Use minimal log levels in production.
Complete Documentation
For the full API reference, ProGuard/R8 rules, and additional integration patterns, visit:
Resources
- Repository: github.com/trustpin-cloud/kotlin.sdk
- API Reference: trustpin-cloud.github.io/kotlin.sdk
- Maven Coordinates:
cloud.trustpin:kotlin-sdk:6.2.0 - Dashboard: app.trustpin.cloud
- Support: support@trustpin.cloud