Skip to main content
Version: Next

Usage

Basic Example

import 'package:flutter/material.dart';
import 'package:trustchex_flutter_sdk/trustchex_flutter_sdk.dart';

class VerificationScreen extends StatelessWidget {
final String sessionId;
final String baseUrl;

const VerificationScreen({
super.key,
required this.sessionId,
required this.baseUrl,
});

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Verification')),
body: TrustchexView(
sessionId: sessionId,
baseUrl: baseUrl,
onCompleted: () {
Navigator.of(context).pop();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Verification completed successfully!'),
backgroundColor: Colors.green,
),
);
},
onError: (error) {
Navigator.of(context).pop();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Verification error: $error'),
backgroundColor: Colors.red,
),
);
},
),
);
}
}

Props

PropTypeRequiredDescription
baseUrlStringYour API base URL
sessionIdStringSession identifier
brandingTrustchexBrandingColors and logo
localeTrustchexLocaleLanguage (en, tr)
onCompletedVoidCallbackCalled when the verification process finishes
onErrorFunction(String)Error callback
onDocumentReadFunction(DocumentReadResult)Called when document is read (NFC or OCR)
skipNfcResultScreenboolSkip the NFC document review screen and proceed automatically after a successful NFC scan. Default: false
skipSuccessScreenboolSkip the final success screen and call onCompleted immediately when the process finishes. Default: false

Document Read Event

The onDocumentRead callback fires as soon as document data is available — immediately after a successful NFC chip read or after a successful camera OCR scan. It receives a unified DocumentReadResult object regardless of source.

import 'package:trustchex_flutter_sdk/trustchex_flutter_sdk.dart';

TrustchexView(
baseUrl: 'https://your-api.com',
sessionId: 'session-123',
onDocumentRead: (result) {
final document = result.document;

print(document.documentType.name); // "passport" | "id"
print(document.issuingCountry); // "DEU"
print(document.nationality); // "DEU"
print(document.documentNumber); // "C01X00T478"
print(document.personalNumber); // "8512310074" | null
print(document.lastName); // "MÜLLER"
print(document.firstName); // "JÜRGEN KARL"
print(document.sex.name); // "male" | "female" | "unspecified" | "unknown"
print(document.dateOfBirth); // "1985-03-10" (ISO 8601)
print(document.dateOfExpiry); // "2030-11-05" (ISO 8601)
},
onCompleted: () => print('Done'),
onError: (error) => print('Error: $error'),
)

DocumentReadResult

class DocumentReadResult {
final DocumentData document;
final DocumentName name;
final DocumentFaceImage? face;
}

class DocumentData {
final ScannedDocumentType documentType; // .passport | .idCard
final String issuingCountry; // 3-letter ICAO code, e.g. "TUR"
final String nationality;
final String documentNumber;
final String? personalNumber;
final String lastName; // Best available name, e.g. "MÜLLER"
final String firstName; // Best available name, e.g. "JÜRGEN"
final DocumentSex sex; // .male | .female | .unspecified
final String? dateOfBirth; // ISO 8601, e.g. "1990-05-15"
final String? dateOfExpiry; // ISO 8601, e.g. "2028-03-01"
}

class DocumentName {
final String rawLast; // MRZ ASCII surname
final String rawFirst; // MRZ ASCII given name
final String displayLast; // Best Unicode form
final String displayFirst; // Best Unicode form
final DocumentNameSource source; // .dg11 | .reverseTable | .raw
}

class DocumentFaceImage {
final String data; // Base64-encoded image
final String mimeType; // e.g. "image/jpeg"
}

Name Sources

SourceDescription
dg11Exact printed name read from the NFC chip's DG11 file (UTF-8). Highest accuracy.
reverseTableReconstructed via ICAO 9303 reverse transliteration (e.g. OE→Ö, UE→Ü). Applied for known countries.
rawNo conversion — display equals the raw MRZ ASCII value.

Flow Control

Skip NFC Result Screen

By default, after a successful NFC chip read the SDK shows a review screen where the user can verify their document data before proceeding. Set skipNfcResultScreen to bypass this screen entirely — the SDK advances automatically as soon as the NFC scan completes.

TrustchexView(
baseUrl: 'https://your-api.com',
sessionId: 'session-123',
skipNfcResultScreen: true,
onCompleted: () => print('Done'),
onError: (error) => print('Error: $error'),
)

Use onDocumentRead alongside this prop if you need to access the scanned document data without showing the review screen.

Skip Success Screen

By default, after the verification process completes the SDK shows a success animation for 3 seconds before calling onCompleted. Set skipSuccessScreen to call onCompleted immediately with no animation or delay.

TrustchexView(
baseUrl: 'https://your-api.com',
sessionId: 'session-123',
skipSuccessScreen: true,
onCompleted: () {
// Called immediately when verification is complete
Navigator.of(context).pop();
},
onError: (error) => print('Error: $error'),
)

Both props can be combined:

TrustchexView(
baseUrl: 'https://your-api.com',
sessionId: 'session-123',
skipNfcResultScreen: true,
skipSuccessScreen: true,
onDocumentRead: (result) => print(result.name.displayFirst),
onCompleted: () => Navigator.of(context).pop(),
onError: (error) => print('Error: $error'),
)

With Branding

TrustchexView(
sessionId: sessionId,
baseUrl: 'https://api.trustchex.com',
locale: TrustchexLocale.en,
branding: const TrustchexBranding(
logoUrl: 'https://trustchex.com/logo.png',
primaryColor: Color(0xFF1E40AF),
secondaryColor: Color(0xFFF8FAFC),
tertiaryColor: Color(0xFFDC2626),
),
onCompleted: () {
Navigator.of(context).pop();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Verification completed successfully!'),
backgroundColor: Colors.green,
),
);
},
onError: (error) {
Navigator.of(context).pop();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Verification error: $error'),
backgroundColor: Colors.red,
),
);
},
)

MASAK Tebliğ 32 (Art. 4/C)

For remote KYC of non-Turkish nationals via NFC passport, the SDK runs the Article 4/C checks whenever the server-side workflow enables them per step. The relevant step flags are:

FlagEffect
passiveAuthRun Passive Authentication on the eID scan (read EF.SOD, verify DG-hash integrity on-device, send the SOD for server-side signature + CSCA chaining).
enforceChipVizMatchServer terminal-rejects when the chip↔VIZ verdict is not MATCH.
collectGeolocationCapture a coarse device geolocation during the contract step. See below.
addressProofRequiredRequire a proof document in the address-capture step.
blockedNationalitiesServer rejects documents whose nationality is on the list.
requireOperatorRequire a connected operator in the video interview.
enforcePassiveAuthServer terminal-rejects when Passive Auth comes back TAMPERED.

These are driven entirely by the backend — no client code is required to turn them on. The SDK computes the chip↔VIZ verdict and Passive Authentication integrity on-device and sends the artifacts; the server makes the authoritative decision.

Geolocation (Art. 4/C(1)(e))

Geolocation is captured natively, with no third-party plugin — iOS Core Location, Android LocationManager, through the SDK's own platform channel. When a step has collectGeolocation enabled, the SDK takes a single coarse fix on its own; there is no app-side code to write.

The only setup is declaring the OS permission strings from Installation:

  • iOS NSLocationWhenInUseUsageDescription — Core Location shows the system prompt on first use given this string.
  • Android ACCESS_COARSE_LOCATION — the SDK requests it at runtime.

Everything is fail-soft: if location services are off, permission is denied, or the fix times out, the SDK sends nothing and the flow proceeds — the backend treats a missing fix as "not provided" while still recording that it was expected. The fix is rounded to ~3 decimals (~110 m) before sending.

Deep link functionality is an optional feature if you want to publish a standalone app. To process deep links with the SDK, you need to configure deep links in your app and set up the required hooks to handle incoming URLs.

iOS - Add to Info.plist:

<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>yourapp</string>
</array>
</dict>
</array>

Android - Add to AndroidManifest.xml. Keep android:launchMode="singleTop" (the Flutter default) so a link arriving while the app is open is delivered to the existing activity:

<activity android:name=".MainActivity" android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="yourapp" />
</intent-filter>
</activity>

Add the app_links package — it delivers both cold-start and warm-start links and registers the native handlers (onNewIntent on Android, the scene/URL callbacks on iOS) itself, so no MainActivity or AppDelegate override is required:

dependencies:
app_links: ^6.3.2

Subscribe to the links and feed the parsed sessionId / baseUrl into TrustchexView. Use DeeplinkUtils.parseQrCodeUrl, which parses the app-url/{host}/verification-session/{id} format the backend generates (including the embedded https:// host form):

import 'dart:async';

import 'package:app_links/app_links.dart';
import 'package:flutter/material.dart';
import 'package:trustchex_flutter_sdk/trustchex_flutter_sdk.dart';

class App extends StatefulWidget {
const App({super.key});

@override
State<App> createState() => _AppState();
}

class _AppState extends State<App> {
final AppLinks _appLinks = AppLinks();
StreamSubscription<Uri>? _linkSubscription;
String _baseUrl = 'https://app.trustchex.com';
String _sessionId = '';
String? _lastHandledUrl;

@override
void initState() {
super.initState();
_initDeepLinks();
}

Future<void> _initDeepLinks() async {
// Cold start: the app was launched by the deep link.
final initialUri = await _appLinks.getInitialLink();
if (initialUri != null) _processDeepLink(initialUri.toString());

// Warm start: the link arrives while the app is already running.
_linkSubscription = _appLinks.uriLinkStream.listen(
(uri) => _processDeepLink(uri.toString()),
onError: (_) {},
);
}

void _processDeepLink(String url) {
if (url == _lastHandledUrl) return; // dedup replays on resume
final parsed = DeeplinkUtils.parseQrCodeUrl(url);
final sessionId = parsed['sessionId'];
final baseUrl = parsed['baseUrl'];
if (sessionId != null &&
sessionId.isNotEmpty &&
baseUrl != null &&
baseUrl.isNotEmpty) {
setState(() {
_lastHandledUrl = url;
_baseUrl = baseUrl;
_sessionId = sessionId;
});
}
}

@override
void dispose() {
_linkSubscription?.cancel();
super.dispose();
}

@override
Widget build(BuildContext context) {
return TrustchexView(
baseUrl: _baseUrl,
sessionId: _sessionId.isEmpty ? null : _sessionId,
onCompleted: () => print('Done'),
onError: (error) => print('Error: $error'),
);
}
}

DeeplinkUtils.parseQrCodeUrl parses URLs with the format:

scheme://app-url/your-api.com/verification-session/session-123

And returns a map with baseUrl and sessionId keys.

Sessions

Create sessions using the REST API.

Session Access Methods

The SDK provides multiple ways for users to access their verification session:

Users click a link that opens the app directly with the session:

// Deep link format: yourapp://app-url/your-api.com/verification-session/session-123
TrustchexView(
baseUrl: 'https://your-api.com',
sessionId: 'session-123', // From deep link
onCompleted: () => print('Done'),
)

Benefits:

  • Seamless user experience - one tap to start
  • Direct navigation to verification
  • No manual input required
  • Works great for email and SMS campaigns

Users scan a QR code that contains the session information. The SDK includes a built-in QR scanner accessible from the welcome screen.

Benefits:

  • Quick and contactless
  • No typing required
  • Ideal for in-person verification
  • Built-in scanner included in SDK

3. Session Code Entry (Alternative)

Users can manually enter an 8-character alphanumeric code:

TrustchexView(
baseUrl: 'https://your-api.com',
// No sessionId prop needed - user enters code manually
onCompleted: () => print('Done'),
)

When no sessionId is provided, the SDK displays a screen where users can:

  • Enter the 8-character session code
  • Scan a QR code

Use Cases:

  • Fallback when QR/deep link unavailable
  • Phone/voice support scenarios
  • Print media without QR capability

Creating Sessions

When creating a session via the REST API, you'll receive access methods in the response:

final response = await http.post(
Uri.parse('https://your-api.com/api/v1/verification-sessions'),
headers: {'x-api-key': 'YOUR_API_KEY'},
body: jsonEncode({
'workflowId': 'workflow-123',
'email': 'user@example.com'
}),
);

final data = jsonDecode(response.body);
final deepLink = data['deepLink'];
final qrCodeLink = data['qrCodeLink'];
final sessionCode = data['sessionCode'];

// Use deepLink for direct app navigation (recommended)
// Use qrCodeLink to generate QR code (recommended)
// Use sessionCode as alternative access method