Skip to Content
SDK IntegrationKotlin (Android & JVM)

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

PlatformMinimum Version
AndroidAPI 25+ (full feature support)
JVMJava 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.

KeyTypeRequiredNotes
organization_idStringYesNon-empty
project_idStringYesNon-empty
public_keyStringYesBase64-encoded ECDSA P-256 public key
modeStringNo"strict" (default) or "permissive"
configuration_urlStringNoMust 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

ApproachBest ForSetup Complexity
OkHttp Integration (Recommended)Most Android apps🟢 Low
Retrofit IntegrationREST API clients (uses OkHttp under the hood)🟢 Low
Ktor Client IntegrationKtor-based apps and KMP shared modules🟡 Medium
Manual VerificationCustom transports, non-OkHttp stacks🟠 High

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 preconfigured OkHttpClient 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 sink

Sinks 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) // detach

The 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:

VariantMeaning
DomainNotRegisteredStrict mode and the host isn’t in the configuration
PinsMismatchServer certificate doesn’t match any active pin
AllPinsExpiredConfiguration is stale — rotate pins in the dashboard
InvalidServerCertServer returned an unparseable certificate
InvalidProjectConfigBad credentials or invalid configuration
ErrorFetchingPinningInfoNetwork failure while loading the configuration
ConfigurationValidationFailedJWS signature didn’t verify against the project’s public key
ConfigIntegrityErrorConfiguration failed an integrity check — hard stop
NotInitializedAn API was called before setup() completed successfully
AlreadyInitializedsetup() was called a second time on the same instance
SetupInProgressAn operation raced a setup() that hadn’t finished yet
LockTimeoutAn internal lock couldn’t be acquired in time
TimeoutAn operation (e.g. awaitConfiguration) exceeded its timeout
SSLContextSetupFailedThe pinned SSLContext / socket factory couldn’t be created
UnsupportedDeviceThe 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

  1. Initialize in Application.onCreate() for app-wide coverage.
  2. Use a coroutine scope for async setup — the API is suspend-first (blocking variants are available for synchronous call sites).
  3. Call TrustPin.awaitConfiguration() after setup(), before constructing any HTTP client that depends on pinning — setup() is non-blocking.
  4. Set the log level before setup() to capture initialization output.
  5. Handle setup errors gracefully — don’t block app launch.

Security

  1. Use TrustPinMode.STRICT in production.
  2. Prefer SPKI pinning; rotate pins in the dashboard before they expire.
  3. Monitor pin validation failures via TrustPin.setValidationListener(...) or logging.
  4. Keep credentials outside source control — prefer TrustPinConfiguration.fromAssets(context) with per-flavor trustpin.json files, or fetch them at runtime and use withAndroidStorage(context).
  5. Use HTTPS for all pinned domains.

Performance

  1. Configuration is cached for 10 minutes with a stale-while-revalidate fallback.
  2. Reuse OkHttpClient instances rather than creating one per request.
  3. Use minimal log levels in production.

Complete Documentation

For the full API reference, ProGuard/R8 rules, and additional integration patterns, visit:

TrustPin Kotlin API Reference 


Resources