Skip to Content
SDK IntegrationDart / Flutter

Flutter SDK

Integrate TrustPin into your Flutter application for cross-platform certificate pinning on iOS, Android, and macOS.

Current version: trustpin_sdk 5.0.0 (bundles TrustPinKit 5.0.0 for iOS/macOS and cloud.trustpin:kotlin-sdk 5.0.0 for Android)

Platform Requirements

PlatformMinimum Version
iOS13.0+
AndroidAPI 25+
macOS13.0+

Additional Requirements:

  • iOS/macOS: Swift 6.1+, Xcode 16.3+
  • Android: Kotlin 2.3.0+, Java 11+
  • macOS: network client entitlement required for sandboxed apps

Installation

Add TrustPin to your pubspec.yaml:

dependencies: trustpin_sdk: ^5.0.0

Then install:

flutter pub get

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

setupWithNativeBundle() tells each platform’s native SDK to read its own bundled configuration file, so credentials never enter the Dart isolate.

Drop platform-native config files into your app:

iOSios/Runner/TrustPin-Info.plist:

KeyTypeRequiredNotes
OrganizationIdStringYesNon-empty
ProjectIdStringYesNon-empty
PublicKeyStringYesBase64-encoded
ModeStringNo"strict" (default) or "permissive", lowercase
ConfigurationURLStringNoMust be HTTPS

After adding the file, open the project in Xcode and confirm Target Membership is checked for the Runner target — otherwise the plist won’t be copied into the app bundle and setupWithNativeBundle() will fail.

macOSmacos/Runner/TrustPin-Info.plist (same keys as iOS, same Target Membership requirement).

For sandboxed macOS apps, add the network client entitlement to both macos/Runner/DebugProfile.entitlements and macos/Runner/Release.entitlements:

<key>com.apple.security.network.client</key> <true/>

Androidandroid/app/src/main/assets/trustpin.json:

{ "organization_id": "your-org-id", "project_id": "your-project-id", "public_key": "LS0tLS1CRUdJTi...", "mode": "strict", "configuration_url": "https://your-server.com/pins.jws" }

Gradle includes the JSON in the APK automatically — no pubspec.yaml assets: entry is needed. The plugin’s own AndroidManifest.xml already declares the required network permission.

Make sure your consuming app declares minSdk 25 (or higher) in android/app/build.gradle:

android { defaultConfig { minSdk 25 } }

Then call setupWithNativeBundle() during app startup:

import 'package:flutter/material.dart'; import 'package:trustpin_sdk/trustpin_sdk.dart'; Future<void> main() async { WidgetsFlutterBinding.ensureInitialized(); try { await TrustPin.shared.setupWithNativeBundle(); print('TrustPin initialized successfully'); } on TrustPinException catch (e) { print('TrustPin initialization failed: ${e.code} - ${e.message}'); } runApp(const MyApp()); }

Per-environment file names

setupWithNativeBundle() accepts optional per-platform filenames so you can ship different credentials per build flavor. null (the default) tells each native SDK to use its standard filename (TrustPin-Info.plist / trustpin.json):

await TrustPin.shared.setupWithNativeBundle( iosFileName: 'TrustPin-Staging.plist', macosFileName: 'TrustPin-Staging.plist', androidFileName: 'trustpin-staging.json', );

Validating Connections

TrustPin.shared.validateConnection() is the recommended way to check a host against the active configuration without going through your HTTP client. It performs a TLS handshake and verifies the leaf certificate against your pins.

Future<void> checkServer(String host) async { try { await TrustPin.shared.validateConnection( host, timeout: const Duration(seconds: 5), ); print('Connection is allowed by the configured pins.'); } on TrustPinException catch (e) { print('Validation failed: ${e.code} - ${e.message}'); } }
ParameterTypeDefault
hostString— (positional, required)
portint443
timeoutDurationconst Duration(seconds: 30)

Note: The older fetchCertificate() and verify() pair is deprecated and will be removed in a future major release. Migrate to validateConnection().


Integration Approaches

ApproachBest ForSetup Complexity
Dio HTTP Client (Recommended)Most Flutter apps🟢 Low
Standard http ClientApps using package:http🟢 Low
Manual validateConnection()Custom transports, pre-flight checks🟡 Medium

TrustPinDioInterceptor validates every request before it leaves the device:

import 'package:dio/dio.dart'; import 'package:trustpin_sdk/trustpin_sdk.dart'; final dio = Dio(); dio.interceptors.add(TrustPinDioInterceptor()); try { final response = await dio.get('https://api.example.com/data'); print('Request successful: ${response.statusCode}'); } on DioException catch (e) { if (e.error is TrustPinException) { final trustPinError = e.error as TrustPinException; print('Pinning failed: ${trustPinError.code}'); } }

Standard http Package

Create a pinned http.Client via TrustPinHttpClient.create():

import 'package:http/http.dart' as http; import 'package:trustpin_sdk/trustpin_sdk.dart'; final client = TrustPinHttpClient.create(); try { final response = await client.get(Uri.parse('https://api.example.com/data')); print('Request successful: ${response.statusCode}'); } on TrustPinException catch (e) { print('Pinning failed: ${e.code} - ${e.message}'); } finally { client.close(); }

Named Instances (Multi-Tenant)

If your app talks to multiple TrustPin projects — or if you’re shipping a library that needs its own isolated pinning configuration — ship one set of bundled config files per instance and load them via setupWithNativeBundle() on a named instance. Both TrustPinDioInterceptor and TrustPinHttpClient.create() accept an instance: parameter to bind to a specific named instance:

final pin = TrustPin.instance('com.mylib.networking'); await pin.setupWithNativeBundle( iosFileName: 'TrustPin-MyLib.plist', macosFileName: 'TrustPin-MyLib.plist', androidFileName: 'trustpin-mylib.json', ); // Dio bound to this instance dio.interceptors.add(TrustPinDioInterceptor(instance: pin)); // http.Client bound to this instance final client = TrustPinHttpClient.create(instance: pin);

Repeated calls to TrustPin.instance('id') with the same ID return the same instance.


Logging

Set the desired verbosity before calling setup() to capture initialization logs:

await TrustPin.shared.setLogLevel(TrustPinLogLevel.info);

Available levels: none, error, info, debug.


Error Handling

All SDK errors surface as TrustPinException, which exposes a code and a message plus convenience getters for the common cases:

GetterTrue when
isDomainNotRegisteredStrict mode and the host isn’t in the configuration
isPinsMismatchServer certificate doesn’t match any active pin
isAllPinsExpiredConfiguration is stale — rotate pins in the dashboard
isInvalidServerCertServer returned an unparseable certificate
isInvalidProjectConfigBad credentials or invalid configuration
isErrorFetchingPinningInfoNetwork failure while loading the configuration
isConfigurationValidationFailedJWS signature didn’t verify against the project’s public key
isFetchCertificateTimeoutConnection timed out during certificate retrieval
try { await TrustPin.shared.validateConnection('api.example.com'); } on TrustPinException catch (e) { if (e.isPinsMismatch) { // Show a network-error UI and refuse the request. } else if (e.isDomainNotRegistered) { // Either the host is unexpected, or the configuration is out of date. } else { // Log everything else with e.code and e.message } }

Best Practices

Setup & Initialization

  1. Call WidgetsFlutterBinding.ensureInitialized() before initializing TrustPin.
  2. Initialize in main() before running the app.
  3. Set the log level first to capture initialization output.
  4. 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.
  4. Keep credentials outside source control — prefer setupWithNativeBundle() with platform-native config files (TrustPin-Info.plist / trustpin.json), using iosFileName / androidFileName / macosFileName for per-flavor overrides. Credentials never enter the Dart isolate this way.
  5. Use HTTPS for all pinned domains.

Performance

  1. Configuration is cached for 10 minutes with automatic refresh.
  2. Reuse HTTP client instances rather than creating one per request.
  3. Use minimal log levels in production.

Complete Documentation

For the full API reference, advanced configuration, macOS sandbox setup, and the example app, visit:

TrustPin Flutter API Reference 


Resources