Configuration
All Datafly mobile SDKs share the same configuration options.
Configuration options
| Option | Type | Default | Description |
|---|---|---|---|
pipelineKey | String | Required | Pipeline key from your source configuration |
endpoint | String | Required | Your Signal collection endpoint URL (e.g., https://data.example.com) |
flushInterval | Number | 30 | Seconds between automatic flushes |
flushThreshold | Number | 20 | Number of events queued before automatic flush |
maxQueueSize | Number | 1000 | Maximum events stored in local SQLite queue |
sessionTimeout | Number | 1800 | Seconds of inactivity before starting a new session (30 min) |
trackAppLifecycle | Boolean | true | Auto-track install, update, open, and background events |
collectDeviceIdentifiers | Boolean | true | Attach device identifiers (idfv on iOS, android_id on Android) and the advertising ID |
pinnedCertificateHashes | String[] | [] | Base64 SHA-256 of pinned SubjectPublicKeyInfo |
encryptEventQueue | Boolean | true | Encrypt queued event payloads at rest |
debug | Boolean | false | Enable debug logging |
Device identifiers
collectDeviceIdentifiers controls whether the SDK attaches hardware-scoped
identifiers to events. Set it to false where you do not want a persistent
device identifier leaving the app, or where pairing android_id with an
advertising ID would complicate your Play Data Safety declaration. When it is
off, setAdvertisingId becomes a no-op as well.
iOS
let config = DataflyConfig(
pipelineKey: "dk_live_abc123",
endpoint: "https://data.example.com",
flushInterval: 15,
flushThreshold: 10,
maxQueueSize: 5000,
sessionTimeout: 900, // 15 minutes
trackAppLifecycle: true,
debug: false
)
Datafly.shared.initialize(config: config)Android
val config = DataflyConfig(
pipelineKey = "dk_live_abc123",
endpoint = "https://data.example.com",
flushIntervalSeconds = 15,
flushThreshold = 10,
maxQueueSize = 5000,
sessionTimeoutSeconds = 900,
trackAppLifecycle = true,
debug = false
)
Datafly.initialize(context, config)React Native
Datafly.initialize({
pipelineKey: 'dk_live_abc123',
endpoint: 'https://data.example.com',
flushInterval: 15,
flushThreshold: 10,
maxQueueSize: 5000,
sessionTimeout: 900,
trackAppLifecycle: true,
debug: false,
});Flutter
await Datafly.initialize(DataflyConfig(
pipelineKey: 'dk_live_abc123',
endpoint: 'https://data.example.com',
flushInterval: 15,
flushThreshold: 10,
maxQueueSize: 5000,
sessionTimeout: 900,
trackAppLifecycle: true,
debug: false,
));.NET MAUI
Datafly.Initialize(new DataflyConfig
{
PipelineKey = "dk_live_abc123",
Endpoint = "https://data.example.com",
FlushInterval = 15,
FlushThreshold = 10,
MaxQueueSize = 5000,
SessionTimeout = 900,
TrackAppLifecycle = true,
Debug = false
});Endpoint URL
The endpoint should be your Signal collection endpoint URL. This is the same endpoint used by the web SDK (Datafly.js):
https://data.example.comThe SDK appends /v1/batch for event delivery. Ensure your Signal collection endpoint is accessible from mobile networks (not just your internal network).
Do not include a trailing slash in the endpoint URL. The SDK normalises the endpoint by stripping trailing slashes automatically.
Pipeline key
The pipeline key identifies which source this data belongs to. Create a separate source in the management UI for each mobile app (or use the same source as your web property if you want unified data).
Pipeline keys follow the format: dk_live_ or dk_test_ followed by a random string.
Debug mode
When debug is enabled:
- iOS: Logs to the Xcode console via
os.log(subsystem:com.datafly.signal) - Android: Logs to Logcat with tag
DataflySignal
You can also attach a callback to inspect events before they are queued:
// iOS
Datafly.shared.onEvent = { event in
print("Event: \(event)")
}
// Android
Datafly.onEvent = { event ->
Log.d("Datafly", "Event: $event")
}Disable debug mode in production builds to avoid unnecessary logging overhead.
Challenge tokens
Pipelines with challenge_token_required reject writes that arrive without a
token. Supply one with setChallengeToken, and it is sent as X-Datafly-Token
on every upload:
Datafly.shared.setChallengeToken(token)Datafly.setChallengeToken(token)Pass null to clear it.
Enabling challenge_token_required on a mobile pipeline without calling this
first will reject every event with a 401.
Mobile pipelines and the domain allowlist
A mobile SDK is not a browser and never sends an Origin header, so the
per-pipeline domain allowlist cannot apply to it. Pipelines of type mobile are
exempt from the missing-Origin default-deny rule — without that exemption, a
mobile pipeline that happened to carry a domain allowlist would reject all of
its own traffic with nothing in the response explaining why.
Set the pipeline’s type to Mobile when you create it so this applies.
Transport security
Three controls a security review will ask about.
Egress is enforced, not just intended
The SDK refuses to contact any host other than the endpoint you configure. This is enforced in the transport itself — the request is rejected before it is built, and the TLS handshake is refused for any other host — so “it cannot phone home” is a property that can be demonstrated rather than a claim about the code.
Subdomains are not implicitly allowed, and the check is not prefix-based:
collect.example.com.attacker.com is refused.
Certificate pinning
Off by default, and deliberately so: a pin that outlives its certificate takes your app offline, and this SDK cannot manage rotation for your endpoint.
DataflyConfig(
pipelineKey: "dk_live_...",
endpoint: "https://collect.example.com",
pinnedCertificateHashes: [
"primary-key-hash-base64",
"backup-key-hash-base64", // covers your next key
]
)Pins are SHA-256 of the certificate’s SubjectPublicKeyInfo, base64-encoded — the public key, not the whole certificate, so a pin survives renewal with the same key. Any certificate in the chain may match, so pin an intermediate or root rather than a leaf. Extract one with:
openssl s_client -connect collect.example.com:443 </dev/null 2>/dev/null \
| openssl x509 -pubkey -noout \
| openssl pkey -pubin -outform der \
| openssl dgst -sha256 -binary | base64Pinning is applied on top of system trust validation, never instead of it: a pinned certificate that has expired or been revoked still fails.
Always configure at least one backup pin covering the key you will rotate to. A single pin means your next certificate renewal takes every installed copy of your app offline until users update.
Queue encryption at rest
On by default. The local queue holds whatever properties and traits your app passed, which is frequently PII, and the file is readable on a rooted or jailbroken device and in unencrypted backups.
Payloads are encrypted individually with AES-GCM rather than encrypting the database, which would mean a native dependency the SDK deliberately does not have. Row metadata stays in clear so the queue can still be indexed and trimmed without decrypting.
Keys live in the iOS Keychain and the Android Keystore, so they are not extractable from the app’s data directory. Android needs API 23 for this; below that payloads are stored in clear rather than protected by a key sitting beside the data it protects.
A queue written before encryption was enabled still drains normally.