-
Notifications
You must be signed in to change notification settings - Fork 0
api communication
Welcome back! In RuleManager, we saw how the SDK acts like a bouncer, checking if visitors meet specific criteria before they can participate in an experiment. We've covered how the SDK manages visitors, experiments, features, data, bucketing, and rules internally.
But how does the SDK get the initial setup information (like the details of your experiments and those rules) from Convert's servers? And how does it report back what happened (like which variation a visitor saw, or if they completed a goal)?
Imagine the Convert SDK running in your application is like a local branch office. It needs to:
- Get Instructions: Receive the latest operational plan (your project configuration, including experiments, features, audiences) from the headquarters (Convert servers).
- Send Reports: Send status updates (tracking events like "visitor A saw variation B", "visitor C completed goal X") back to headquarters so you can see the results in your Convert dashboard.
How does this local branch office (the SDK) communicate reliably with headquarters (Convert servers)?
The iOS SDK splits API communication across two focused components rather than a single ApiManager:
-
ConfigFetchService(inSources/ConvertSwiftSDK/) — fetches and caches the project configuration. It composes aURLSessionHTTPClienttransport (a concreteHTTPClientport adapter backed byURLSession) with aCoordinatedFileStorefor write-through on-disk caching. -
EventQueue(inSources/ConvertSwiftSDKCore/Event/) — an actor that batches produced tracking entries and ships them through aURLSessionEventUploader. For durable delivery after app suspension or termination it hands off to aBackgroundSessionManagerthat uses a dedicated backgroundURLSession.
Both components are wired together in ConvertSwiftSDK.init (the composition root) and operate entirely behind the public API — you rarely interact with either directly.
When the SDK initialises with an sdkKey, a detached Task in ConvertSwiftSDK.init builds a ConfigFetchService and runs a two-phase load:
-
Cache hit first:
loadCachedConfig()reads the on-disk cache from the Application Support directory. A cache miss is silent; corrupt bytes are deleted and logged as WARN. -
Live fetch:
fetchLiveConfig()issues an HTTP GET to the config endpoint, write-through caches the verbatim response bytes (not a re-encode), and returns the decodedProjectConfig.
The URL shape is {apiConfigEndpoint}/config/{sdkKey}, where apiConfigEndpoint defaults to https://cdn-4.convertexperiments.com/api/v1 (from ConvertConfiguration.defaultAPIBase). When environment is set a ?environment=... query item is appended; networkCacheLevel == .low adds ?_conv_low_cache=1.
Every outbound request — config fetch and event delivery alike — carries User-Agent: ConvertAgent/<version>. The URLSessionHTTPClient stamps this header last (replacing any caller-supplied value) so the bot-gate bypass is always in effect.
sequenceDiagram
participant SDK as ConvertSwiftSDK (init Task)
participant CFS as ConfigFetchService
participant Cache as On-disk cache (Application Support)
participant HTTP as URLSessionHTTPClient
participant CDN as Convert Config CDN
SDK->>+CFS: loadCachedConfig()
CFS->>+Cache: read(from: cacheURL)
Cache-->>-CFS: Data (or miss)
CFS-->>-SDK: ProjectConfig? (nil on miss/corrupt)
SDK->>+CFS: fetchLiveConfig()
Note over CFS: Build URL: {apiConfigEndpoint}/config/{sdkKey}
CFS->>+HTTP: get(url:, headers: {Authorization?})
Note over HTTP: Stamps User-Agent: ConvertAgent/<version>
HTTP->>+CDN: GET /api/v1/config/{sdkKey}
CDN-->>-HTTP: Raw JSON bytes (200 OK)
HTTP-->>-CFS: (Data, HTTPURLResponse)
CFS->>Cache: write(verbatimBytes, to: cacheURL)
CFS-->>-SDK: ProjectConfig
The decoded ProjectConfig is stored in ConfigStore, which fires SystemEvent.ready once and resumes all waitForReady() continuations. Post-ready refreshes fire SystemEvent.configUpdated instead.
The EventQueue actor is the EventSink every ConvertContext enqueues bucketing and conversion events through. It never sends an event immediately; instead it batches by two triggers:
-
Size trigger — once the buffer reaches
eventsBatchSize(defaultDefaults.batchSizefromConvertConfiguration) the queue drains immediately without blocking the enqueueing caller. -
Interval trigger — a timer loop sleeps
eventsReleaseIntervalMsms (defaultDefaults.releaseIntervalMs) then flushes whatever is buffered. The timer starts lazily on the firstenqueueand runs onTask.sleep(nanoseconds:)(the iOS 15–safe form).
On a successful flush the queue fires SystemEvent.apiQueueReleased carrying the event count. On failure it restores the drained entries to the buffer in their original order so the next cycle re-delivers them.
The tracking endpoint URL is {apiTrackEndpoint}/track/{sdkKey}, assembled inside URLSessionEventUploader. Like the config fetch, every request carries User-Agent: ConvertAgent/<version>.
Wire source is "ios-sdk" (stamped into the tracking envelope; the JS SDK uses "js-sdk" by default).
sequenceDiagram
participant Ctx as ConvertContext
participant EQ as EventQueue (actor)
participant Up as URLSessionEventUploader
participant Track as Convert Track API
Ctx->>+EQ: enqueue(.bucketing(…), for: visitorId)
Note over EQ: Add to buffer. Buffer size = 1.
Ctx->>+EQ: enqueue(.conversion(…), for: visitorId)
Note over EQ: Add to buffer. Buffer size = 2.
loop When batchSize reached OR release interval fires
EQ->>EQ: drain() — copy buffer, clear buffer
Note over EQ: Assemble visitors:[{visitorId, events}] payload
EQ->>+Up: upload(payload)
Up->>+Track: POST /api/v1/track/{sdkKey} (JSON)
Note over Up: User-Agent: ConvertAgent/<version>
Track-->>-Up: 200 OK
Up-->>-EQ: success
EQ->>EQ: fire .apiQueueReleased (eventCount)
end
The iOS SDK guarantees delivery even when the app is suspended or killed before a flush:
-
On-disk persistence (
CoordinatedFileEventQueueStore) — when the app goes to the background,LifecycleObservertriggers the queue to write its buffer to disk. On the nextstart()(cold-start recovery), pending entries are read back and re-loaded into the buffer. -
Background
URLSession(BackgroundSessionManager) — uses a separate backgroundURLSessionwith a SDK-owned identifier so uploads can complete while the app is suspended. The integrator forwardsapplication(_:handleEventsForBackgroundURLSession:completionHandler:)throughsdk.handleEventsForBackgroundURLSession(identifier:completionHandler:)for prompt OS-level completion acknowledgement (optional, for efficiency — the zero-config durability guarantee holds without it).
All values live on ConvertConfiguration (passed to ConvertSwiftSDK.init(configuration:)):
| Property | Default | Effect |
|---|---|---|
apiConfigEndpoint |
"https://cdn-4.convertexperiments.com/api/v1" |
Base URL for config fetch |
apiTrackEndpoint |
"https://cdn-4.convertexperiments.com/api/v1" |
Base URL for event delivery |
eventsBatchSize |
Defaults.batchSize |
Events per flush batch |
eventsReleaseIntervalMs |
Defaults.releaseIntervalMs |
Flush interval (ms) |
networkTracking |
true |
Global tracking on/off (static, init-time) |
networkCacheLevel |
.normal |
CDN cache hint; .low adds _conv_low_cache=1
|
A runtime tracking toggle is available via await sdk.setTrackingEnabled(false) / isTrackingEnabled() — see Tracking Control.
| Aspect | Browser / Node.js (JS SDK) | Server-side (PHP SDK) | iOS SDK |
|---|---|---|---|
| HTTP client | Built-in HttpClient (fetch/XHR) |
PSR-18 auto-discovered client |
URLSession via URLSessionHTTPClient
|
| Queue flush trigger |
setTimeout release interval |
register_shutdown_function at request end |
Size trigger + Task.sleep interval timer |
| Background delivery |
navigator.sendBeacon() on page unload |
Not applicable (request-scoped) | Persistent disk buffer + background URLSession; LifecycleObserver triggers persist-on-background |
| Release interval | Configurable (default 10 000 ms) | None | Configurable via eventsReleaseIntervalMs
|
| Retry / durability | Log on failure; no automatic retry | 2 retries with 100 ms / 300 ms backoff | Failed flush restores buffer for the next cycle; on-disk persistence survives process kill |
| Timer management |
startQueue() / stopQueue()
|
None | Lifecycle-driven — LifecycleObserver persists on background / flushes on foreground |
The iOS SDK's communication layer is split into two focused, actor-safe components:
-
ConfigFetchServicefetches the project configuration (ProjectConfig) viaURLSession, caches it to Application Support, and supplies it toConfigStorewhich signals readiness. -
EventQueuecollects tracking entries produced byConvertContext, batches them by size and interval, and ships them throughURLSessionEventUploader— with durable background delivery viaBackgroundSessionManagerand on-disk persistence viaCoordinatedFileEventQueueStore.
Both components stamp every outbound request with User-Agent: ConvertAgent/<version> so Convert's bot filter accepts them, and wire source is "ios-sdk" on the tracking payload.
Let's explore the internal notification system the SDK uses when important events happen next: EventManager!
Copyrights © 2026 All Rights Reserved by Convert Insights, Inc.
Getting Started
iOS SDK
- Quickstart
- Installation
- Initialization
- Configuration
- Return Types & Models
- Code Examples
- Offline Behavior
- Tracking Control
- App Privacy & Data Collection
- Objective-C Interop
Core Concepts
- Experiences & Variations
- Feature Flags
- Bucketing Algorithm
- Rule Evaluation
- Segments
- Data Management
- Event System
- API Communication
How-To Guides
- Running Experiences
- Running Features
- Tracking Conversions
- Visitor Context
- Persistent Storage
- Troubleshooting
Contributing