React Native SDK
Integrate TrustPin into your React Native application for certificate pinning on iOS and Android.
Current version: @trustpin/react-native 6.2.0
Pinning is configured and activated in native code, before any JavaScript runs, so it cannot be weakened from JS, including from over-the-air JS updates. The JavaScript API is observe-only: it reports readiness and events but holds no lever that disables or reconfigures pinning.
Platform Requirements
| Platform | Minimum Version |
|---|---|
| iOS | 15.0+ |
| Android | API 25+ (Android 7.1) |
| React Native | 0.85+ (New Architecture only) |
| Node | 22.11+ |
Additional Requirements:
- iOS: added to the app target’s Copy Bundle Resources phase
- Android: Kotlin 2.3.0+,
minSdk 25(RN’s default is 24)
No macOS support. This SDK targets iOS and Android only. macOS support will follow once react-native-macos reaches the RN 0.85 line.
Installation
npm install @trustpin/react-native
# or
yarn add @trustpin/react-nativeThen follow the setup for your project type below (Expo or bare React Native).
Quick Start
1. Get Your Credentials
Sign in to the TrustPin Dashboard and retrieve:
- Organization ID
- Project ID
- Public Key (Base64-encoded)
Credentials are not secret, but they identify your project. Keep them out of public source control.
2. Set Up TrustPin
TrustPin is bootstrapped in native code before the JavaScript runtime does any networking. The setup differs for Expo and bare React Native projects.
Expo
Add the config plugin to app.json / app.config.js with your credentials:
{
"expo": {
"plugins": [
["@trustpin/react-native", {
"organizationId": "your-org-id",
"projectId": "your-project-id",
"publicKey": "LS0tLS1CRUdJTi...",
"mode": "strict"
}]
]
}
}Then generate the native projects and run:
npx expo prebuild
npx expo run:ios # or: npx expo run:androidThe plugin writes the native config files, wires the native init call on both platforms, and applies the Android toolchain requirements (Kotlin 2.3.0, minSdk 25). Expo Go cannot run pinning, it is native code, so use a development build. Prefer app.config.js with environment variables to keep credentials out of public source.
The config plugin accepts these props:
| Prop | Type | Notes |
|---|---|---|
organizationId | String | Required unless using configFile |
projectId | String | Required unless using configFile |
publicKey | String | Base64 verification key. Required unless using configFile |
mode | strict | permissive | Defaults to strict |
configurationUrl | String | Optional. HTTPS endpoint for a self-hosted signed config |
logLevel | none | error | info | debug | Passed to the native init helper, so it also covers startup logging |
ios.configFile | String | Path to an existing TrustPin-Info.plist instead of generating one |
android.configFile | String | Path to an existing trustpin.json instead of generating one |
android.allowNonOemImages | Boolean | Default false. Allows release builds on non-OEM device OS images (real devices only, not emulators) |
Inline credentials and a
configFilefor the same platform are rejected rather than silently resolved, and partial credentials are rejected too, naming what is missing.
Bare React Native
Bare apps ship the native config files and add one native init call per platform.
Step 1 — Ship the configuration files
iOS — add ios/<YourApp>/TrustPin-Info.plist and add it to the app target’s Copy Bundle Resources phase in Xcode:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>OrganizationId</key>
<string>your-org-id</string>
<key>ProjectId</key>
<string>your-project-id</string>
<key>PublicKey</key>
<string>LS0tLS1CRUdJTi...</string>
<key>Mode</key>
<string>strict</string>
</dict>
</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 |
Android — add android/app/src/main/assets/trustpin.json (Gradle bundles it automatically):
{
"organization_id": "your-org-id",
"project_id": "your-project-id",
"public_key": "LS0tLS1CRUdJTi...",
"mode": "strict",
"configuration_url": "https://your-server.com/pins.jws"
}Step 2 — Call the native init helper
iOS — in your native app bootstrap, such as AppDelegate.swift:
import TrustPinReactNative
func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
TrustPinReactNative.start() // or: .start(logLevel: .debug)
// ...existing React Native setup...
}Android — in your native app bootstrap, such as MainApplication.kt:
import cloud.trustpin.reactnative.TrustPinReactNative
override fun onCreate() {
TrustPinReactNative.start(this) // or: .start(this, TrustPinLogLevel.DEBUG)
super.onCreate()
loadReactNative(this)
}Step 3 — Align the Android Kotlin toolchain
The native runtime requires an Android Kotlin toolchain version compatible with its shipped metadata. Align the Kotlin plugin version used by the app build with the runtime requirement in android/build.gradle:
buildscript {
ext {
kotlinVersion = "2.3.0"
minSdkVersion = 25 // TrustPin requires 25; RN's default is 24
}
dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.3.0")
}
}Then cd ios && pod install, and rebuild the app.
Using the SDK
Pinning is already active, ordinary requests are validated with no extra code:
// This request is pinned inside the TLS handshake. A pin mismatch fails it.
const response = await fetch('https://api.example.com/data');The JavaScript API is for observing that enforcement. A common pattern is to hold first requests until the signed configuration is verified, and to log validation events:
import TrustPin from '@trustpin/react-native';
// Fail-closed readiness gate: resolves once the configuration is verified.
try {
await TrustPin.awaitConfiguration(10_000);
} catch (error) {
// Do NOT fall through to an unpinned client, treat this as a hard stop.
console.error('TrustPin configuration unavailable', error);
}
// Definitive pin verdicts (domain + code + timestamp; no certificate material).
const subscription = TrustPin.onValidationEvent(event => {
if (event.code) {
console.warn(`Pinning rejected ${event.domain}: ${event.code}`);
}
});
// subscription.remove() when you are done.Fail-closed readiness:
awaitConfiguration(timeoutMs?)resolves once the signed configuration is fetched, verified, and active, and rejects withFETCH_CERTIFICATE_TIMEOUTon timeout (the native side clamps the timeout to 10–120 s).isConfigurationLoaded()is a non-throwingPromise<boolean>status check. Skip the gate and the first pinned request simply waits for the configuration to become ready.
Validating Connections
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.
import TrustPin from '@trustpin/react-native';
async function checkServer(host: string) {
try {
await TrustPin.validateConnection(host, 443, 5_000);
console.log('Connection is allowed by the configured pins.');
} catch (error) {
console.error(`Validation failed: ${error.code} - ${error.message}`);
}
}| Parameter | Type | Default |
|---|---|---|
host | string | — (required) |
port | number | 443 |
timeoutMs | number | native default |
API Reference
Import the default export, or named members:
import TrustPin, { TrustPinError, TrustPinErrorCodes } from '@trustpin/react-native';| Method | Description |
|---|---|
awaitConfiguration(timeoutMs?) | Fail-closed readiness gate. Resolves once the signed configuration is fetched, verified, and active. Rejects FETCH_CERTIFICATE_TIMEOUT on timeout. The native side clamps the timeout to 10–120 s. |
isConfigurationLoaded() | Promise<boolean> — whether a validated configuration is currently loaded. |
validateConnection(host, port?, timeoutMs?) | Manually validates the TLS certificate of host:port against the pins (port defaults to 443). Resolves on success, rejects with a stable code otherwise. |
setLogLevel(level) | Sets native log verbosity: 'none' | 'error' | 'info' | 'debug'. |
onValidationEvent(listener) | Subscribes to definitive pin verdicts. Returns { remove() }. |
onLogEvent(listener) | Subscribes to native TrustPin log output. Returns { remove() }. |
Events buffered before JavaScript is alive (cold-start pin failures) are replayed to the first subscriber of each stream.
interface TrustPinValidationEvent {
domain: string;
code: string | null; // null = success; else a failure code
timestampMs: number;
}
interface TrustPinLogEvent {
level: 'error' | 'info' | 'debug';
message: string;
timestampMs: number;
}Monitoring Pin Validation
TrustPin.onValidationEvent() surfaces the native SDKs’ validation telemetry, the signal to use for reporting suspected MITM attempts to your backend:
const subscription = TrustPin.onValidationEvent(event => {
if (event.code) {
// Definitive failure verdicts only: PINS_MISMATCH, ALL_PINS_EXPIRED,
// DOMAIN_NOT_REGISTERED (strict mode). Events carry the domain,
// failure code, and timestamp — no certificate material.
securityMonitor.report(event);
}
});
// subscription.remove() when you are done.Events are observe-only: the verdict is decided before the event is emitted, so a listener cannot veto, approve, or alter a connection. Transient conditions (configuration fetch failures, timeouts) and permissive-mode connections to unregistered domains produce no events. Verdicts buffered before JavaScript is alive (cold-start pin failures) are replayed to the first subscriber.
Logging
Set the desired verbosity to capture SDK logs. In bare projects the native init helper also accepts a startup log level (start(logLevel:) / start(this, ...)); in Expo, set the logLevel plugin prop.
import TrustPin from '@trustpin/react-native';
await TrustPin.setLogLevel('info');Available levels: none, error, info, debug.
Log Stream
TrustPin.onLogEvent() routes native SDK log output into your app’s logging pipeline:
const subscription = TrustPin.onLogEvent(event => {
myLogger.log(`[${event.level}] ${event.message}`);
});
// subscription.remove() when you are done.Error Handling
Every rejection carries a stable string code. JS-side failures are a TrustPinError; native rejections carry the same { code, message } shape, so error.code works uniformly:
import TrustPin, { TrustPinErrorCodes } from '@trustpin/react-native';
try {
await TrustPin.validateConnection('api.example.com');
} catch (error) {
switch (error.code) {
case TrustPinErrorCodes.PINS_MISMATCH: // certificate matched no pin — possible MITM
case TrustPinErrorCodes.DOMAIN_NOT_REGISTERED: // strict mode: domain not in your config
case TrustPinErrorCodes.ALL_PINS_EXPIRED: // every pin for the domain has expired
default:
console.error(error.code, error.message);
}
}Common codes:
| Code | Meaning |
|---|---|
PINS_MISMATCH | Server certificate doesn’t match any active pin |
ALL_PINS_EXPIRED | Every pin for the domain has expired — rotate pins in the dashboard |
DOMAIN_NOT_REGISTERED | Strict mode and the host isn’t in the configuration |
INVALID_SERVER_CERT | Server returned an unparseable certificate |
FETCH_CERTIFICATE_TIMEOUT | Connection timed out during certificate retrieval |
INVALID_PROJECT_CONFIG | Bad credentials or invalid configuration (also raised if the native init helper was never wired) |
INVALID_ARGUMENTS | An argument to an API call was invalid |
Android additionally surfaces UNSUPPORTED_DEVICE, SETUP_IN_PROGRESS, LOCK_TIMEOUT, and SSL_CONTEXT_SETUP_FAILED.
Best Practices
Setup & Initialization
- Configure in native code — the Expo config plugin or the bare init helper — so pinning is active before any JavaScript networking.
- Gate first requests on
awaitConfiguration()to fail closed before the first pinned request. - Set the log level early to capture initialization output.
- Don’t fall through to an unpinned client if configuration is unavailable — treat it as a hard stop.
Security
- Use
strictmode in production. - Prefer SPKI pinning; rotate pins in the dashboard before they expire.
- Monitor pin validation failures via
TrustPin.onValidationEvent(). - Keep credentials outside public source control. Prefer
app.config.jswith environment variables (Expo) or ship the native config files (TrustPin-Info.plist/trustpin.json). Credentials never enter the JavaScript bundle. - Use HTTPS for all pinned domains.
Performance
- Configuration is cached with automatic refresh.
- The JavaScript API is observe-only, there is no per-request overhead beyond the native TLS validation.
- Use minimal log levels in production.
Complete Documentation
For the full API reference and advanced configuration, visit:
TrustPin React Native API Reference
Resources
- Package: npmjs.com/package/@trustpin/react-native
- API Reference: trustpin-cloud.github.io/react-native.sdk
- Dashboard: app.trustpin.cloud
- Support: support@trustpin.cloud