Offline Mode

Offline Mode lets your app present a consent experience (Banner, Preference Center, Vendor List) and collect consent when the device has no internet connection or when the OneTrust CMP API is unreachable. You supply a pre-captured JSON snapshot of each CMP API response, and the SDK uses that snapshot as a template to initialize itself locally.


Table of Contents

  1. Quick Start
  2. How It Works
  3. Capturing the Offline Data
  4. Supplying Logos
  5. When Does the SDK Use Offline Data?
  6. Fallback Priority (Network Failure)
  7. Data Sanitization
  8. Geo-Validation Warnings
  9. Cached vs. Offline Data (Per-Component Mixing)
  10. Limitations
  11. What Changed in 202604.1.0
  12. How It Worked Before 202604.1.0
  13. FAQ

1. Quick Start

// 1. Prepare your offline JSON data (captured from CMP APIs)
func loadOfflineData() {
    let bannerData   = dataFromBundle("OneTrustBannerData")
    let pcData       = dataFromBundle("OneTrustPCData")
    let vendorsData  = dataFromBundle("OneTrustVendorsData")   // only if IAB TCF is in scope

    let cmpApiData = OTOfflineData.CmpApiData(
        cmpBannerData: bannerData,
        cmpPreferencesData: pcData,
        cmpVendorsData: vendorsData
    )

    // Optional: supply logos that would normally be downloaded
    let logos = OTOfflineData.LogoData(
        bannerLogo: UIImage(named: "bannerLogo"),
        pcLogo: UIImage(named: "pcLogo"),
        ageGateLogo: nil,
        attLogo: nil
    )

    let offlineData = OTOfflineData(cmpApiData: cmpApiData, logoData: logos)

    // 2. Set offline data BEFORE calling startSDK
    OTPublishersHeadlessSDK.shared.setOTOfflineData(offlineData)
}

// Helper
func dataFromBundle(_ fileName: String) -> Data? {
    guard let url = Bundle.main.url(forResource: fileName, withExtension: "json") else { return nil }
    return try? Data(contentsOf: url)
}

// 3. Call startSDK — set loadOffline to true to force offline mode for the entire session,
//    or false to use offline data only as a fallback when the network call fails.
loadOfflineData()
OTPublishersHeadlessSDK.shared.startSDK(
    storageLocation: "cdn.cookielaw.org",
    domainIdentifier: "your-domain-id",
    languageCode: "en",
    params: OTSdkParams(countryCode: "US", regionCode: "CA"),
    loadOffline: false          // false = online-first with offline fallback
                                // true = offline mode for entire session (prevents all on-demand network calls to fetch data)
) { response in
    if let warning = response.warning {
        print("SDK initialized with warning: \(warning)")
    }
    if response.status {
        print("SDK ready")
    }
}

The rest of this document explains the behavior in detail.


2. How It Works

┌──────────────────────────────────────────────────────────────┐
│  App calls setOTOfflineData(_:) then startSDK(loadOffline:)  │
└────────────────────────────┬─────────────────────────────────┘
                             │
                ┌────────────▼────────────┐
                │  loadOffline == true ?   │
                └──┬──────────────────┬───┘
                 YES                  NO
                   │                   │
          ┌────────▼────────┐  ┌───────▼────────────┐
          │  Use offline    │  │  Fetch from CMP API │
          │  data directly  │  └───────┬─────────────┘
          └────────┬────────┘          │
                   │          ┌────────▼────────┐
                   │          │  Network failed? │
                   │          └──┬───────────┬──┘
                   │           YES           NO
                   │            │             │
                   │   ┌────────▼────────┐    │  ✅ Use live data
                   │   │  Fallback chain │    │
                   │   │  (see §6)       │    │
                   │   └────────┬────────┘    │
                   │            │              │
                   ▼            ▼              ▼
          ┌──────────────────────────────────────┐
          │  SDK initialized → completion called │
          └──────────────────────────────────────┘

Key points:

  • setOTOfflineData(_:) must be called before startSDK.
  • The SDK sanitizes the offline data, stripping user-specific consent strings before using it (see §7).
  • The data subject identifier comes from startSDK params (or the SDK's default UUID), not from the offline JSON.
  • If cached data from a previous successful online session exists, it takes precedence over offline data on a per-component basis (see §9).
  • When loadOffline: true, the SDK remains in offline mode for the entire session, preventing all on-demand network calls to fetch data (e.g., Vendor List fetch, logo downloads, etc.) until the next startSDK call. The consent logging will still make a network call if network is available.

3. Capturing the Offline Data

The SDK expects the raw JSON responses from three CMP API endpoints:

ComponentAPI EndpointRequired?
Banner(GET) /cfw/cmp/v1/bannerYes — always required
Preference Center(GET) /cfw/cmp/v1/preferencesYes — always required
Vendors(GET) /cfw/cmp/v1/vendorsOptional — if missing, the SDK will initialize successfully but the Vendor List screen will not be accessible until the next online session

How to capture

  1. Make the API calls from an API platform or environment for your target geolocation rule (e.g., EU, US, etc.).
  2. Save each response body as a .json file.
  3. Bundle the files with your app (e.g. in your app's main bundle or a remote config store).

Tip: Capture the data for each geolocation rule/language combination your app supports. The SDK can only ingest one data set at a time — if your app serves multiple regions, store each variation and pass in the appropriate one based on the user's locale.


4. Supplying Logos

If your consent UI displays logos (Banner logo, Preference Center logo, Age Gate, ATT prompt), those images are normally downloaded by the SDK at runtime. In offline mode, the SDK cannot download them, so you can supply them via OTOfflineData.LogoData:

let logos = OTOfflineData.LogoData(
    bannerLogo: UIImage(named: "bannerLogo"),    // nil if not used
    pcLogo: UIImage(named: "pcLogo"),            // nil if not used
    ageGateLogo: nil,
    attLogo: nil
)

Pass nil for any logo your template does not use.


5. When Does the SDK Use Offline Data?

The SDK enters offline mode when all of the following are true:

ConditionDetails
Offline data is setsetOTOfflineData(_:) was called with valid CmpApiData
SDK is in offline modeEither loadOffline: true was passed to startSDK, or the device has no network connectivity
No old-flow data existsThe SDK does not have legacy (pre-CMP-API) cached data on disk. Old-flow users must complete at least one successful online session to migrate to CMP API before offline mode becomes available.

Session-scoped behavior: When loadOffline: true, the SDK remains in offline mode for the entire session (until the next startSDK call or app restart). This prevents all on-demand network calls, including:

  • Vendor List (Global Vendor List) fetch when navigating to the Vendor List screen
  • Logo image downloads
  • Any other network-dependent operations

This ensures a consistent offline experience throughout the session, even if network connectivity becomes available.

If any condition is not met, the SDK proceeds with the normal online flow.


6. Fallback Priority (Network Failure)

When the CMP API network call fails (timeout, server error, no connectivity) and loadOffline was not set to true, the SDK attempts recovery in this order:

PriorityFallbackWhat happens
1Offline data + cached data mixingIf offline data was supplied via setOTOfflineData, the SDK loads Banner and PC (required), and Vendors (optional). For each component, cached data is preferred if it exists; otherwise, offline data is used. This fills in any gaps — e.g., cached Banner + offline PC/Vendors. If Vendors data is not available (neither cached nor offline), the SDK still initializes successfully, but the Vendor List screen will not be accessible.
2Cached CMP data onlyIf no offline data was supplied but cached data exists from a previous successful session, the SDK re-uses it.
3Original errorIf neither offline nor cached data is available, the OTResponse returns the original network error.

When the SDK falls back to cached or offline data, OTResponse.warning is populated with a message indicating that stale/offline data was used. OTResponse.status is still true (success), and OTResponse.error is nil.


7. Data Sanitization

Before parsing offline JSON, the SDK automatically strips user-specific consent strings to prevent one user's consent decisions from being applied to another:

Field removed / clearedReason
otConsentStringSet to empty — the SDK will generate a fresh consent string tied to the current user's identifier
tcString (IAB TC String)Removed — contains encoded consent choices from the original capture
gppTcfString (GPP string)Removed — same reason as above
googleAdditionalConsentRemoved — Google AC string from original capture

This means offline data acts as a template — it provides the UI structure, purpose definitions, and configuration, but not prior consent decisions. Users interacting with the SDK offline will start with the default consent state defined by the template.


8. Geolocation-Validation Warnings

The SDK performs a best-effort geolocation check by comparing the countryCode and regionCode passed to startSDK against the AppConfig embedded in the offline Banner JSON. If there is a mismatch (e.g., you pass countryCode: "US" but the offline data was captured for "DE"), the SDK logs a warning:

⚠️ Geo mismatch detected: SDK country code 'US' does not match offline data country code 'DE'.
   The offline template may not be appropriate for this user's region.

This is informational only — the SDK will still initialize. It is the app's responsibility to supply the correct offline data for the user's region.


9. Cached vs. Offline Data (Per-Component Mixing)

Starting with 202604.2.0, the SDK supports per-component mixing of cached and offline data:

ComponentCached exists?Offline provided?Result
BannerYesYesCached Banner used (preserves user consent)
BannerNoYesOffline Banner used (template)
Preference CenterYesYesCached PC used
Preference CenterNoYesOffline PC used
VendorsYes (and PC cached)YesCached Vendors used
VendorsNo (but PC cached)YesVendors skipped (same-source constraint)
VendorsNoYes (and PC offline)Offline Vendors used
VendorsNoNoVendors unavailable (SDK still initializes, Vendor List not accessible)

Why this matters: Imagine a user who has completed consent on the Banner but has never opened the Preference Center. Their device then goes offline. With per-component mixing, the SDK uses:

  • Cached Banner (preserving the user's prior consent interaction)
  • Offline Preference Center (from the template, since no cached PC exists)
  • Offline Vendors (from the template)

This gives the best possible offline experience while respecting existing user consent.

Note: Preference Center and Vendors must be sourced together (both cached or both offline) to maintain data consistency since Vendors depend on Preference Center purpose definitions. If the Preference Center data is cached but Vendors is not cached, the SDK will not load offline Vendors data — it will skip Vendors entirely. The SDK will still initialize successfully, but the Vendor List screen will not be accessible until the next online session.

Vendors data is optional: Starting with 202604.2.0, the SDK can initialize successfully even if Vendors data is completely unavailable (neither cached nor offline). This matches the online flow behavior where Vendors are fetched on-demand when the user navigates to the Vendor List. If Vendors data is missing:

  • The SDK setup completes successfully
  • Banner and Preference Center work normally
  • The Vendor List screen will not be accessible
  • A warning is logged: "Vendors data is not available for offline mode. The Vendor List will not be accessible until the next successful online session."

10. Limitations

LimitationDetails
Single data setThe SDK accepts one set of offline data at a time. For multi-region apps, the app must determine the correct data set and supply it before startSDK.
No consent syncing offlineConsent decisions made while offline are stored locally. They are not synced to the OneTrust server until the next successful online session.
Session-scoped offline modeWhen loadOffline: true, the SDK remains in offline mode for the entire session (until the next startSDK call or app restart), preventing all on-demand network calls even if connectivity becomes available.
No Universal Consent (UCP)UC Purposes are not supported in offline mode. Only Banner, Preference Center, and Vendor List are supported.
Template only — no prior consentOffline data is sanitized; users start with default consent states. If you need to preserve a returning user's consent offline, ensure they have completed at least one online session (which creates cached data that takes precedence).
Old-flow migration requires onlineApps that previously used the legacy (non-CMP-API) SDK flow must complete one successful online session to migrate to CMP API before offline mode becomes available.
Geolocation accuracy is the app's responsibilityThe SDK cannot perform IP-based geo-detection offline. The app must supply the offline data matching the user's region/regulations.
Logo images must be bundledThe SDK cannot download logos offline. Supply them via OTOfflineData.LogoData or they will not appear.

11. What Changed in 202604.2.0

New behavior

ChangeDescription
Per-component cached + offline mixingPreviously, if any cached CMP data existed, the SDK would skip offline mode entirely. Now, the SDK evaluates each component (Banner, PC, Vendors) individually — using cached data where available and falling back to offline data for missing components.
Vendors data is now optionalThe SDK can initialize successfully even if Vendors data is completely unavailable (neither cached nor offline). The Vendor List screen will simply not be accessible until the next online session. This matches the online flow where Vendors are fetched on-demand.
Session-scoped loadOffline flagWhen loadOffline: true, the SDK remains in offline mode for the entire session, preventing all on-demand network calls (e.g., Vendor List fetch, logo downloads) until the next startSDK call or app restart. Previously, the flag was reset after initial setup, allowing subsequent network calls.
Offline data as a network-failure fallbackWhen loadOffline: false and the CMP API call fails, the SDK now tries offline data (with per-component mixing) before falling back to cached-only data. This means supplying offline data improves resilience even for online-first apps.
Data sanitizationOffline JSON is automatically stripped of otConsentString, tcString, gppTcfString, and googleAdditionalConsent before parsing. This prevents one user's consent from leaking to another.
Geolocation-validation warningsThe SDK compares startSDK country/region params against the offline data's AppConfig and logs warnings on mismatch.
OTResponse.warning propertyA new property on OTResponse communicates when the SDK initialized using cached or offline data instead of live data. status is true, error is nil, and warning contains the explanation.
Identifier from startSDKThe data subject identifier is taken from startSDK params (or SDK default), not from the offline JSON. This ensures the correct user identity is associated with consent.

Removed / deprecated

ItemDetails
Old/legacy flow offline dataOnly CMP API offline data is accepted. Passing legacy (non-CMP-API) offline data to setOTOfflineData is a no-op with an error log.

12. How It Worked Before 202604.2.0

Prior to this version, offline mode had several limitations:

AspectPrevious behaviorProblem
All-or-nothing cached checkIf any cached CMP API data existed for the current profile, the SDK would skip offline mode entirely — even if only the Banner was cached and PC/Vendors were missing.Users who had completed Banner consent but never opened the Preference Center could not get a working PC experience offline. The SDK would either show an incomplete UI or fail.
No network-failure fallbackWhen loadOffline: false and the CMP API call failed, the SDK only fell back to cached data. If no cache existed, it returned an error — even if offline data had been supplied by the app.Apps that supplied offline data as a safety net got no benefit from it unless loadOffline: true was explicitly set.
No sanitizationOffline JSON was used as-is, including otConsentString, tcString, and other user-specific fields from the original capture.One user's consent decisions could be unintentionally applied to another user.
No geo-validationThe SDK did not compare startSDK country/region params against the offline data.Apps could accidentally serve a GDPR template to a US user (or vice versa) with no warning.
No warning on stale dataWhen the SDK fell back to cached data, there was no programmatic way for the app to know.Apps couldn't inform users that the consent experience might be outdated.

These challenges motivated the redesign in 202604.2.0, which introduces per-component mixing, automatic sanitization, geo warnings, and the OTResponse.warning property.


13. FAQ

Q: Do I need to call setOTOfflineData every time before startSDK?
Yes. The offline data is held in memory and is not persisted by the SDK. Call setOTOfflineData before each startSDK invocation.

Q: What happens if I pass loadOffline: true but don't call setOTOfflineData?
The SDK will fail to initialize and return an error in the OTResponse.

Q: Can I update the offline data between app launches?
Yes. You can update the bundled JSON files in an app update, or fetch fresh snapshots from a remote config service and supply them to setOTOfflineData.

Q: If the user consents offline, when does it sync to the server?
On the next successful startSDK call with network connectivity. The SDK stores pending consent receipts locally and posts them when the network is available.

Q: Does offline mode work on tvOS?
Yes. The same APIs and behavior apply to both iOS and tvOS.

Q: What if my app supports multiple languages?
Each CMP API snapshot is language-specific. Capture and bundle one set of JSON files per language. Determine the user's language at runtime and pass the matching data to setOTOfflineData.

Q: What if I only have Banner data but not Preference Center or Vendors?
The SDK requires Banner and Preference Center data to initialize successfully. Vendors data is optional — if you only supply Banner and PC data (and no cached Vendors exist), the SDK will initialize successfully, but the Vendor List screen will not be accessible until the next online session. This is acceptable for apps that don't use IAB TCF or Google ATP.

Q: What happens if I call startSDK(loadOffline: true) and then network becomes available mid-session?
The SDK remains in offline mode for the entire session. The loadOffline flag persists until the next startSDK call or app restart. This prevents on-demand network calls (e.g., Vendor List fetch, logo downloads) even if connectivity is restored. To switch back to online mode, call startSDK(loadOffline: false) again.

Q: Why does the SDK skip offline Vendors data when Preference Center is cached but Vendors is not cached?
Preference Center and Vendors must come from the same source (both cached or both offline) to maintain data consistency since Vendors depend on Preference Center purpose definitions. Mixing cached Preference Center with offline Vendors could result in mismatched data. The SDK prioritizes data consistency over completeness — it will skip Vendors entirely rather than risk inconsistency. The SDK still initializes successfully; the Vendor List is simply not accessible until the next online session.

Q: How do I know if the SDK used offline/cached data instead of live data?
Check OTResponse.warning in the startSDK completion handler. If it is non-nil, the SDK used a fallback path and the warning message describes which one.