Skip to Content
ConfigurationEnterprise Features

Enterprise Features

Tracelet comes equipped with several high-security features designed for enterprise use cases such as military tracking, HIPAA-compliant patient monitoring, and cryptographically verified logistics.


1. Data Encryption (SQLCipher)

When Tracelet records a location in the background, it temporarily stores it in an internal SQLite database before syncing it to your server. If a device is stolen, physical access could potentially allow an attacker to read these historical routes.

Tracelet solves this by offering At-Rest Database Encryption using SQLCipher.

How Data Encryption Works

  1. When you enable the encryptDatabase flag, the underlying Rust SQLite database is instantly encrypted with AES-256 via SQLCipher.
  2. By default, Tracelet automatically generates a secure 256-bit key and stores it safely in the platform’s native secure storage (Android Keystore / iOS Keychain). You don’t have to manage the key manually!
  3. All data written to the disk becomes completely unreadable without the key.
  4. Existing unencrypted databases are automatically migrated to encrypted when the flag is enabled.

Implementation

To enable encryption using the platform-managed secure key (Recommended):

final config = Config.balanced( security: tl.SecurityConfig( encryptDatabase: true, // This MUST be true to enable encryption ), );

Custom Key Management (Advanced)

If your app prefers to manage its own key material (e.g., using flutter_secure_storage), you can provide a custom encryptionKey. Note that encryptDatabase must still be true.

// Generate a secure random key final customKey = await tl.Tracelet.generateEncryptionKey(); await secureStorage.write(key: 'my_db_key', value: customKey); // Pass it to Tracelet final config = Config.balanced( security: tl.SecurityConfig( encryptDatabase: true, encryptionKey: await secureStorage.read(key: 'my_db_key'), ), );

2. Audit Trail Management & Verification

In sensitive logistics or law enforcement, you must be able to mathematically prove that a location record was not tampered with, spoofed, or altered after the fact. Tracelet provides an Audit Trail feature to create a cryptographic chain of custody.

How Audit Trail Management Works

When AuditConfig.enabled is set to true, Tracelet does not just save latitude and longitude. It creates a “blockchain-like” record for every location. The underlying Rust engine ensures this process is atomic and foolproof:

  1. The Genesis Block: When the engine first starts on a new device, it calculates a genesis hash: SHA-256("tracelet:genesis:{device_id}").
  2. Canonical String Generation: For each location, Tracelet generates a strict, deterministic string format combining the previous hash and the new location’s exact data.
  3. Hashing: This canonical string is hashed (using SHA-256 by default), producing the hash for the current location.
  4. The Chain: This hash becomes the previous_hash for the next location, creating an unbroken chain. If an attacker modifies even a single coordinate of a past location in the database, the hash of that location will change, breaking the entire chain of subsequent hashes.

Configuration

final config = Config.highAccuracy( audit: tl.AuditConfig( enabled: true, hashAlgorithm: tl.HashAlgorithm.sha256, ), );

The JSON Payload

When Tracelet syncs these locations to your backend, the JSON payload for each location will directly contain the audit fields.

{ "device": "tracelet-example", "locationCount": 1, "locations": [ { "uuid": "loc-1234-abcd", "timestamp": "2024-01-01T00:00:00Z", "is_moving": true, "coords": { "latitude": 37.774900, "longitude": -122.419400, "accuracy": 10.0, "speed": 15.5, "heading": 90.0, "altitude": 100.0 }, "audit_hash": "a1b2c3d4e5f6...", "audit_previous_hash": "f6e5d4c3b2a1...", "audit_chain_index": 42 } ] }

Server-Side Verification (How to Verify the Code)

To verify the integrity of the data on your server, you must recreate the exact canonical string that the Tracelet Rust core uses and compare the resulting hash against the audit_hash field in the payload.

1. Build the Canonical String

The string MUST be formatted exactly like this, separated by the pipe | character: {audit_previous_hash}|TRACELET_AUDIT|{audit_chain_index}|{uuid}|{latitude}|{longitude}|{timestamp}|{accuracy}|{speed}|{heading}|{altitude}|{is_moving}

Important Formatting Rules (Based on Tracelet’s Rust Core):

  • latitude and longitude: Formatted to exactly 6 decimal places (e.g., 37.774900).
  • accuracy, speed, heading, altitude: Formatted to exactly 2 decimal places (e.g., 10.00).
  • is_moving: Formatted as 1 (true) or 0 (false).

Example Canonical String:

f6e5d4c3b2a1...|TRACELET_AUDIT|42|loc-1234-abcd|37.774900|-122.419400|2024-01-01T00:00:00Z|10.00|15.50|90.00|100.00|1

2. Hash and Compare

Pass the constructed canonical string through a standard SHA-256 hashing function and convert it to a lowercase hexadecimal string.

  • If your calculated hash matches the audit_hash field: The location is mathematically proven to be untampered.
  • If it does not match: The location data has been altered. You should immediately flag this record and investigate the device.

3. Location Accuracy Profiles (HF / LF)

When reviewing the Tracelet Example App, you will notice buttons for LF Accuracy and HF Accuracy. These map directly to Tracelet’s pre-built Configuration Profiles.

  • LF (Low Frequency / Low Accuracy): This maps to the Config.lowPower() profile. It restricts the engine to use Cell-Tower and Wi-Fi triangulation only (no GPS). It is highly battery efficient but provides a wider accuracy radius.
  • HF (High Frequency / High Accuracy): This maps to the Config.highAccuracy() profile. It forces the physical GPS chip on, providing 1-5 meter accuracy at the cost of higher battery drain.

4. Carbon Footprint Estimator & Trip Reports

Tracelet includes a built-in CarbonEstimator utility. It fuses activity recognition (detecting if the user is driving, walking, or cycling) with precise geographic distance to calculate the estimated CO₂ emissions for a trip. This is crucial for sustainability reporting, carbon-offset programs, or ESG (Environmental, Social, and Governance) compliance.

How it works

  1. You instantiate a CarbonEstimator.
  2. When the user starts moving (isMoving == true), you call startTrip().
  3. You feed real-time activity changes (e.g., switching from walking to driving) and locations into the estimator.
  4. When the user stops moving (isMoving == false), you call endTrip() to generate a TripCarbonSummary.

Implementation Example

final carbonEstimator = tl.CarbonEstimator(); // 1. Listen for motion changes to start and end trips Tracelet.onMotionChange((loc) { if (loc.isMoving) { carbonEstimator.startTrip(); } else { final summary = carbonEstimator.endTrip(); if (summary != null) { print('Trip ended: \${summary.totalCarbonGrams}g CO2'); print('Total Distance: \${summary.totalDistanceMeters}m'); print('Dominant Mode: \${summary.dominantMode}'); // e.g. "driving" } } }); // 2. Feed Activity Recognition data to detect the transport mode Tracelet.onActivityChange((evt) { // Activity names map to emissions factors internally (e.g., driving vs walking) carbonEstimator.setActivity(evt.activity.name); }); // 3. Feed location coordinates to accumulate distance accurately Tracelet.onLocation((loc) { carbonEstimator.onLocationReceived( loc.coords.latitude, loc.coords.longitude, ); });

The resulting TripCarbonSummary can then be synced to your server or displayed directly to the user to encourage eco-friendly transport choices.


5. Remote Configuration

Fetched and applied natively on both iOS and Android since 3.6.2. Earlier versions accepted remoteConfigUrl but never fetched it — the native side silently fell back to the local config.

For fleets and large deployments you often need to change tracking behavior — accuracy, sync cadence, geofence radius, battery budget — without shipping an app update. Tracelet’s Remote Configuration lets the SDK pull a config document from your own HTTPS endpoint and apply it on top of the local config at runtime.

How Remote Configuration Works

  1. You set remoteConfigUrl (and optional headers) in your AppConfig.
  2. On ready(), the SDK first applies the last successfully fetched config from an on-device cache — so a relaunch resumes on your latest published settings instantly and offline, before any network call, and without blocking ready().
  3. It then fetches a fresh copy from your endpoint in the background and applies it through the same runtime path as setConfig() — restarting the active tracking pipeline only when a tracking-relevant key actually changed.
  4. It keeps the config fresh by re-fetching on the remoteConfigRefreshInterval cadence (in minutes).

Only HTTPS URLs are honored — an http:// URL is rejected and logged, because the configuration controls tracking behavior and must not be tamperable in transit.

The Endpoint Contract

Your endpoint answers a GET with a JSON object shaped like a Tracelet config map — either flat, or the nested { "geo": {…}, "app": {…}, "http": {…} } form that Config.toMap() produces. Only the keys you include are overridden; every other setting keeps its local value.

{ "geo": { "distanceFilter": 25.0, "desiredAccuracy": 0 }, "app": { "heartbeatInterval": 120 }, "http": { "autoSync": true } }

Configuration

final config = Config.balanced().copyWith( app: tl.AppConfig( remoteConfigUrl: 'https://config.mycompany.com/tracelet/fleet-a.json', remoteConfigHeaders: {'Authorization': 'Bearer $token'}, remoteConfigTimeout: 15000, // milliseconds remoteConfigRefreshInterval: 60, // minutes ), );
FieldTypeDefaultDescription
remoteConfigUrlString?nullHTTPS endpoint returning the JSON config map. null disables remote config.
remoteConfigHeadersMap<String, String>?nullHeaders sent with the fetch (e.g. auth token).
remoteConfigTimeoutint60000Fetch timeout in milliseconds.
remoteConfigRefreshIntervalint1440Minutes between background refreshes (floored at 15 minutes natively). 0 fetches once and never repeats.

Applied remote values are merged into the device’s persisted config, and remote overrides win over local ones. Only publish values you intend every device fetching that URL to adopt — scope per-fleet or per-cohort by pointing each build at a different URL.

Observing applied remote config from Dart

Remote config is fetched and applied on the native side. Since 3.6.10, every time a remote override is applied — both the cached copy restored at ready() and each background refresh — the SDK surfaces it back to Dart:

  • Tracelet.activeConfig (and therefore tracelet_doctor and the Dart-side battery-budget engine) now reflects the fetched values, instead of only the last config you set locally.
  • Subscribe to react when a server-driven change lands:
// Callback — fires after a remote override is applied. // Tracelet.activeConfig already reflects the new values when this fires. Tracelet.onRemoteConfig((Config config) { print('Remote battery budget: ${config.geo.batteryBudgetPerHour}%/hr'); }); // …or listen to the broadcast stream directly. final sub = Tracelet.remoteConfigStream.listen((config) { // react to the merged, now-active config });

Before 3.6.10, remote values were applied to native tracking but were not mirrored into Tracelet.activeConfig, so diagnostics could still show the last locally-set value even though tracking already used the remote one.