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
- Quick Start
- How It Works
- Capturing the Offline Data
- Supplying Logos
- When Does the SDK Use Offline Data?
- Fallback Priority (Network Failure)
- Data Sanitization
- Geo-Validation Warnings
- Cached vs. Offline Data (Per-Component Mixing)
- Limitations
- What Changed in 202604.1.0
- How It Worked Before 202604.1.0
- FAQ
1. Quick Start
// 1. Prepare your offline JSON data (captured from CMP APIs)
@Nullable
String readAssetFile(@NonNull Context context, @NonNull String fileName) {
String fileContent = null;
InputStream inputStream = null;
try {
inputStream = context.getAssets().open(fileName);
int size = inputStream.available();
byte[] buffer = new byte[size];
int isRead = inputStream.read(buffer);
Log.v(LOG_TAG, "bytes read for Asset file : " + isRead);
inputStream.close();
fileContent = new String(buffer);
} catch (IOException e) {
Log.e(LOG_TAG, "File read error = " + e.getMessage());
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
Log.e(LOG_TAG, "Error on closing FileInputStream message = " + e.getMessage());
}
}
}
return fileContent;
}
// Optional: supply logos that would normally be downloaded
public OTConfiguration getOTConfiguration(@NonNull Context context) {
OTConfiguration.OTConfigurationBuilder otConfigurationBuilder = OTConfiguration.OTConfigurationBuilder.newInstance();
otConfigurationBuilder = otConfigurationBuilder.setBannerLogo(logo_location);
otConfigurationBuilder = otConfigurationBuilder.setPCLogo(logo_location);
OTConfiguration otConfig = otConfigurationBuilder.build()
}
// pass the otConfig instance as a param to the UI methods - showBannerUI()/setupUI()/showPreferenceCenterUI()
new OTPublishersHeadlessSDK(this).showBannerUI(RenderNativeUIActivity.this, otConfig);
private void setOfflineData() throws JSONException {
JSONObject offlineData = new JSONObject();
String bannerData = readAssetFile(this, “banner json file name here”);
if (!TextUtils.isEmpty(bannerData)) {
offlineData.put("bannerData", new JSONObject(bannerData));
}
String pcData = readAssetFile(this, “pc json file name here”);
if (!TextUtils.isEmpty(pcData)) {
offlineData.put("preferenceCenterData", new JSONObject(pcData));
}
String vendorData = readAssetFile(this, “vendor json file name here”);
if (!TextUtils.isEmpty(vendorData)) {
offlineData.put("vendorListData", new JSONObject(vendorData));
}
// 2. Set offline data BEFORE calling startSDK
otPublishersHeadlessSDK.setOTOfflineData(offlineData);
// 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.
otPublishersHeadlessSDK.startSDK(domainURL, domainId, languageCode, sdkParams, false, new OTCallback() {
@Override
public void onSuccess(@NonNull OTResponse otSuccessResponse) {
String otData = otSuccessResponse.getResponseData();
//consent logic
//UI logic
}
@Override
public void onFailure(@NonNull OTResponse otErrorResponse) {
int errorCode = otErrorResponse.getResponseCode();
String errorDetails = otErrorResponse.getResponseMessage();
// Use toString() to log complete OT response
Log.i(LOG_TAG, otErrorResponse.toString());
}
});
}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 beforestartSDK.- The SDK sanitizes the offline data, stripping user-specific consent strings before using it (see §7).
- The data subject identifier comes from
startSDKparams (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 nextstartSDKcall. 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:
| Component | API Endpoint | Required? |
|---|---|---|
| Banner | (GET) /cfw/cmp/v1/banner | Yes — always required |
| Preference Center | (GET) /cfw/cmp/v1/preferences | Yes — always required |
| Vendors | (GET) /cfw/cmp/v1/vendors | Optional — if missing, the SDK will initialize successfully but the Vendor List screen will not be accessible until the next online session |
How to capture
- Make the API calls from an API platform or environment for your target geolocation rule (e.g., EU, US, etc.).
- Save each response body as a
.jsonfile. - 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), those images are normally downloaded by the SDK at runtime. In offline mode, the SDK cannot download them, so you can supply them via OTConfiguration.OTConfigurationBuilder:
public OTConfiguration getOTConfiguration(@NonNull Context context) {
OTConfiguration.OTConfigurationBuilder otConfigurationBuilder = OTConfiguration.OTConfigurationBuilder.newInstance();
otConfigurationBuilder = otConfigurationBuilder.setBannerLogo(logo_location);
otConfigurationBuilder = otConfigurationBuilder.setPCLogo(logo_location);
OTConfiguration otConfig = otConfigurationBuilder.build()
}
// pass the otConfig instance as a param to the UI methods - showBannerUI()/setupUI()/showPreferenceCenterUI()
new OTPublishersHeadlessSDK(this).showBannerUI(RenderNativeUIActivity.this, otConfig);5. When Does the SDK Use Offline Data?
The SDK enters offline mode when all of the following are true:
| Condition | Details |
|---|---|
| Offline data is set | setOTOfflineData(_:) was called with valid CmpApiData |
| SDK is in offline mode | Either loadOffline: true was passed to startSDK, or the device has no network connectivity |
| No old-flow data exists | The 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:
| Priority | Fallback | What happens |
|---|---|---|
| 1 | Offline data + cached data mixing | If 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. |
| 2 | Cached CMP data only | If no offline data was supplied but cached data exists from a previous successful session, the SDK re-uses it. |
When the SDK falls back to cached or offline data, the OTResponse object as part of the success callbacks will include 19 -> SDK initialized with cached data or 20 -> SDK initialized with offline data .
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 / cleared | Reason |
|---|---|
otConsentString | Set 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 |
googleAdditionalConsent | Removed — 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 prints an info level log:
"Geolocation change detected. " +
"Previous: " + savedCountry + "/" + savedRegion +
", New: " + newCountryCode + "/" + newRegionCode
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:
| Component | Cached exists? | Offline provided? | Result |
|---|---|---|---|
| Banner | Yes | Yes | Cached Banner used (preserves user consent) |
| Banner | No | Yes | Offline Banner used (template) |
| Preference Center | Yes | Yes | Cached PC used |
| Preference Center | No | Yes | Offline PC used |
| Vendors | Yes (and PC cached) | Yes | Cached Vendors used |
| Vendors | No (but PC cached) | Yes | Vendors skipped (same-source constraint) |
| Vendors | No | Yes (and PC offline) | Offline Vendors used |
| Vendors | No | No | Vendors 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
10. Limitations
| Limitation | Details |
|---|---|
| Single data set | The 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 offline | Consent decisions made while offline are stored locally. They are not synced to the OneTrust server until the next successful online session. |
| Session-scoped offline mode | When 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 consent | Offline 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 online | Apps 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 responsibility | The 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 bundled | The SDK cannot download logos offline. Supply them via OTOfflineData.LogoData or they will not appear. |
11. What Changed in 202604.2.0
New behavior
| Change | Description |
|---|---|
| Per-component cached + offline mixing | Previously, 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 optional | The 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 flag | When 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 fallback | When 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 sanitization | Offline JSON is automatically stripped of otConsentString, tcString, gppTcfString, and googleAdditionalConsent before parsing. This prevents one user's consent from leaking to another. |
| Geolocation-validation warnings | The SDK compares startSDK country/region params against the offline data's AppConfig and logs warnings on mismatch. |
OTResponse.warning property | A 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 startSDK | The 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
| Item | Details |
|---|---|
| Old/legacy flow offline data | Only 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:
| Aspect | Previous behavior | Problem |
|---|---|---|
| All-or-nothing cached check | If 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 fallback | When 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 sanitization | Offline 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-validation | The 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 data | When 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 Android TV?
Yes. The same APIs and behavior apply to Android TV.
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.
Updated about 8 hours ago