Core Concepts
This page covers the foundational architecture and design decisions behind AppDNA. Understanding these concepts will help you integrate the SDK effectively and get the most out of the platform.SDK Architecture
The AppDNA SDK follows a singleton pattern with module namespaces. After callingconfigure(), the SDK is accessible through a single global instance. Each feature area is organized as a namespace on that instance:
| Namespace | Purpose |
|---|---|
push | Push notification registration and handling |
billing | Subscription status, product fetching, purchases |
onboarding | Server-driven onboarding flow presentation |
paywall | Server-driven paywall rendering and events |
remoteConfig | Key-value remote configuration |
features | Feature flags and entitlements |
experiments | A/B experiment assignment and exposure tracking |
surveys | In-app survey presentation and response collection |
inAppMessages | Triggered in-app messaging |
deepLinks | Deep link routing and deferred deep links |
- iOS
- Android
- Flutter
- React Native
Offline-First Design
AppDNA SDKs are built for unreliable networks. The SDK never assumes connectivity is available and degrades gracefully when it is not.Configuration Priority
The SDK resolves configuration using a three-tier fallback:| Source | Description | Latency |
|---|---|---|
| Remote | Real-time sync from Firestore. Always preferred when available. | ~100ms |
| Cached | Last known good config, persisted in local storage. Expires after the configured TTL. | ~1ms |
| Bundled | Static JSON file shipped with the app binary. Used on first launch before any network call completes. | ~1ms |
The default config TTL is 5 minutes. After the TTL expires, the SDK attempts a remote fetch. If the fetch fails, the cached config continues to be used until a successful refresh.
Event Queue
Events are never dropped. When the SDK records an event:- The event is written to a persistent on-disk queue (Keychain on iOS, SharedPreferences on Android).
- The queue is auto-flushed every 30 seconds or when it reaches 20 events, whichever comes first.
- If the flush fails (no connectivity, server error), events remain in the queue and are retried on the next flush cycle.
- Events are only removed from the queue after a successful server acknowledgment.
Because events are persisted to disk, they survive app restarts and even device reboots. No event is ever lost due to a crash or force-quit.
Config Bundle
A config bundle is a versioned JSON snapshot generated server-side. It contains all active configuration for your app: onboarding flows, paywalls, in-app messages, experiments, feature flags, and remote config values.How It Works
- The dashboard generates a new bundle version whenever you publish a change.
- The SDK polls
GET /api/v1/sdk/config-bundle/versionto check for updates. - If a newer version exists, the SDK downloads the full bundle and caches it locally.
- The bundle can also be embedded in your app binary for zero-latency first launch.
Embedding a Bundle
For the best first-launch experience, embed the latest config bundle in your app:- iOS
- Android
- Flutter
- React Native
Add
appdna-config.json to your Xcode project as a bundle resource.Experiments
AppDNA experiments use deterministic bucketing to assign users to variants. No server call is required — the assignment is computed locally on the device.How Bucketing Works
The SDK computes a bucket using:| Input | Source |
|---|---|
userId | The identified user ID (or anonymous ID if not identified) |
experimentId | Unique identifier for the experiment |
salt | Random string generated when the experiment is created |
Key Properties
- Deterministic: The same user always gets the same variant for the same experiment. No randomness, no server dependency.
- Cross-platform consistent: A user who opens your app on iOS and Android will see the same variant because the hash function and inputs are identical.
- Session-stable: The variant does not change between sessions or app restarts.
- Exposure tracking: The SDK records an exposure event once per session when
getVariant()is called. This prevents inflated exposure counts from multiple reads.
- iOS
- Android
- Flutter
- React Native
Delegates and Callbacks
All SDK modules use a delegate/callback pattern to communicate events back to your application. This keeps the SDK non-blocking and lets you respond to events in your own code.- iOS
- Android
- Flutter
- React Native
Modules expose protocols with default empty implementations. Conform to only the methods you need:
Environments
AppDNA supports two environments to separate development and production data:| Environment | API Base URL | Key Prefix |
|---|---|---|
| Production | https://api.appdna.ai | adn_live_ |
| Sandbox | https://sandbox-api.appdna.ai | adn_test_ |
- Events, experiments, and configuration are completely isolated between environments.
- Use sandbox during development and testing. Switch to production for App Store / Play Store builds.
- The SDK determines the environment from the API key prefix — there is no need to set it separately if the key is correct.
Webhooks
AppDNA can send real-time HTTP callbacks to your server whenever key events occur. Webhooks let you sync data to your backend, trigger workflows, or feed events into third-party tools.Event Types
AppDNA supports 16 webhook event types spanning the full lifecycle:| Category | Events |
|---|---|
| User | user.created, user.identified, user.trait_updated |
| Subscription | subscription.started, subscription.renewed, subscription.cancelled, subscription.expired |
| Experiment | experiment.exposure, experiment.conversion |
| Paywall | paywall.presented, paywall.dismissed, paywall.purchase_started, paywall.purchase_completed |
| Push | push.delivered, push.opened |
| Onboarding | onboarding.completed |
Security
Every webhook payload is signed with HMAC-SHA256. Your server should verify the signature before processing:Reliability
- 5 retry attempts with exponential backoff (1s, 5s, 30s, 2min, 10min).
- Your endpoint must respond with a
2xxstatus within 15 seconds or the attempt is considered failed. - After 50 consecutive failures, the webhook endpoint is automatically disabled and you receive an email notification.
- You can view delivery logs and manually retry failed deliveries in the dashboard.
To test webhooks locally during development, use a tunneling tool like ngrok and point your webhook endpoint to the tunnel URL.
Identity
AppDNA manages user identity through a combination of anonymous and authenticated identifiers.Anonymous ID
On first launch, the SDK generates a random anonymous ID and persists it securely:| Platform | Storage |
|---|---|
| iOS | Keychain |
| Android | SharedPreferences (encrypted) |
| Flutter | Platform-specific (Keychain on iOS, SharedPreferences on Android) |
| React Native | Platform-specific (Keychain on iOS, SharedPreferences on Android) |
Linking Identity
When you callidentify(userId:), the SDK links the anonymous ID to the provided user ID. All past events tracked under the anonymous ID are retroactively associated with the user.
Resetting Identity
Callingreset() clears the user ID but keeps the anonymous ID. This is appropriate for sign-out flows where another user may sign in on the same device.
- iOS
- Android
- Flutter
- React Native
The anonymous ID is never deleted by
reset(). This ensures continuity for device-level analytics even across multiple user sessions.