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
| Platform | Minimum Version |
|---|---|
| iOS | 13.0+ |
| Android | API 25+ |
| macOS | 13.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
pub.dev (Recommended)
Add TrustPin to your pubspec.yaml:
dependencies:
trustpin_sdk: ^5.0.0Then install:
flutter pub getQuick 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:
iOS — ios/Runner/TrustPin-Info.plist:
| Key | Type | Required | Notes |
|---|---|---|---|
OrganizationId | String | Yes | Non-empty |
ProjectId | String | Yes | Non-empty |
PublicKey | String | Yes | Base64-encoded |
Mode | String | No | "strict" (default) or "permissive", lowercase |
ConfigurationURL | String | No | Must be HTTPS |
After adding the file, open the project in Xcode and confirm Target Membership is checked for the
Runnertarget — otherwise the plist won’t be copied into the app bundle andsetupWithNativeBundle()will fail.
macOS — macos/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/>Android — android/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}');
}
}| Parameter | Type | Default |
|---|---|---|
host | String | — (positional, required) |
port | int | 443 |
timeout | Duration | const Duration(seconds: 30) |
Note: The older
fetchCertificate()andverify()pair is deprecated and will be removed in a future major release. Migrate tovalidateConnection().
Integration Approaches
| Approach | Best For | Setup Complexity |
|---|---|---|
| Dio HTTP Client (Recommended) | Most Flutter apps | 🟢 Low |
Standard http Client | Apps using package:http | 🟢 Low |
Manual validateConnection() | Custom transports, pre-flight checks | 🟡 Medium |
Dio HTTP Client (Recommended)
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:
| Getter | True when |
|---|---|
isDomainNotRegistered | Strict mode and the host isn’t in the configuration |
isPinsMismatch | Server certificate doesn’t match any active pin |
isAllPinsExpired | Configuration is stale — rotate pins in the dashboard |
isInvalidServerCert | Server returned an unparseable certificate |
isInvalidProjectConfig | Bad credentials or invalid configuration |
isErrorFetchingPinningInfo | Network failure while loading the configuration |
isConfigurationValidationFailed | JWS signature didn’t verify against the project’s public key |
isFetchCertificateTimeout | Connection 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
- Call
WidgetsFlutterBinding.ensureInitialized()before initializing TrustPin. - Initialize in
main()before running the app. - Set the log level first 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.
- Keep credentials outside source control — prefer
setupWithNativeBundle()with platform-native config files (TrustPin-Info.plist/trustpin.json), usingiosFileName/androidFileName/macosFileNamefor per-flavor overrides. Credentials never enter the Dart isolate this way. - Use HTTPS for all pinned domains.
Performance
- Configuration is cached for 10 minutes with automatic refresh.
- Reuse HTTP client instances rather than creating one per request.
- 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
- Repository: github.com/trustpin-cloud/flutter.sdk
- Package: pub.dev/packages/trustpin_sdk
- API Reference: trustpin-cloud.github.io/flutter.sdk
- Dashboard: app.trustpin.cloud
- Support: support@trustpin.cloud