> For the complete documentation index, see [llms.txt](https://cmp.docs.sirdata.net/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://cmp.docs.sirdata.net/en/mobile-apps-native-sdk/mobile-sdk/sharing-consent.md).

# Sharing consent with vendor SDKs

This is the part that makes advertising work, and it is also the part that needs the least code from you.

Like every IAB-compliant CMP, Sirdata CMP publishes consent through the standard **in-app storage keys**. Compliant vendor SDKs — Google Mobile Ads, Prebid Mobile, AppLovin, Amazon APS and others — read those keys on their own. For the most common SDKs that do not, or not entirely, the Sirdata SDK ships **consent adapters** that forward each choice for you.

| Platform | Storage                                | How vendors observe it                            |
| -------- | -------------------------------------- | ------------------------------------------------- |
| Android  | `SharedPreferences` (the default file) | `PreferenceManager.getDefaultSharedPreferences()` |
| iOS      | `NSUserDefaults.standard`              | `UserDefaults.didChangeNotification`              |

## SDK by SDK

What each common SDK needs, checked against the real libraries.

| SDK                                   | Reads the IAB keys                     | What the Sirdata SDK does | What you do                                                       |
| ------------------------------------- | -------------------------------------- | ------------------------- | ----------------------------------------------------------------- |
| Google Mobile Ads (AdMob, Ad Manager) | Yes                                    | —                         | Nothing                                                           |
| Firebase Analytics                    | Only under the interoperability option | Consent Mode adapter      | Nothing — [details](#firebase-analytics)                          |
| AppsFlyer                             | Only once told to                      | Consent adapter           | `enableTCFDataCollection(true)` — [details](#appsflyer)           |
| Adjust                                | No                                     | Consent adapter           | Nothing — [details](#adjust-and-branch)                           |
| Branch                                | No                                     | Consent adapter           | Nothing — [details](#adjust-and-branch)                           |
| Meta (Facebook SDK)                   | No                                     | —                         | Gate it yourself — [details](#sdks-that-do-not-read-the-iab-keys) |

Versions checked: Google Mobile Ads 25.5.0 (Android); Firebase Analytics 23.2.0 (Android) and 12.19.2 (iOS); AppsFlyer 7.0.1 (Android) and 7.0.2 (iOS); Adjust 5.8.0; Branch 5.21.3 (Android) and 3.14.2 (iOS); Meta 18.3.0 (Android — the Meta iOS SDK was not measured).

Google Mobile Ads needs no code at all: its ad requests carry the stored TC string, the Additional Consent string and the GPP string. Initialize Sirdata CMP first, and load ads once consent is collected.

## The keys the SDK maintains

**IAB TCF v2.2**

| Key                                                                                            | Content                                                                                                   |
| ---------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| `IABTCF_CmpSdkID`                                                                              | `92` — written synchronously on `initialize()`                                                            |
| `IABTCF_CmpSdkVersion`                                                                         | `10`, the CMP version the TC string carries — not the SDK version (2.0.0). Written with `IABTCF_CmpSdkID` |
| `IABTCF_PolicyVersion`                                                                         | TCF policy version, from the loaded Global Vendor List (`5` on a first launch until it loads)             |
| `IABTCF_gdprApplies`                                                                           | `1` / `0`                                                                                                 |
| `IABTCF_PublisherCC`                                                                           | Publisher country code                                                                                    |
| `IABTCF_PurposeOneTreatment`                                                                   | Purpose 1 special treatment flag                                                                          |
| `IABTCF_UseNonStandardTexts`                                                                   | Non-standard texts flag                                                                                   |
| `IABTCF_TCString`                                                                              | The TC string                                                                                             |
| `IABTCF_VendorConsents` / `IABTCF_VendorLegitimateInterests`                                   | Vendor bit strings                                                                                        |
| `IABTCF_DisclosedVendors`                                                                      | Disclosed vendors bit string                                                                              |
| `IABTCF_PurposeConsents` / `IABTCF_PurposeLegitimateInterests`                                 | Purpose bit strings                                                                                       |
| `IABTCF_SpecialFeaturesOptIns`                                                                 | Special feature opt-ins                                                                                   |
| `IABTCF_PublisherRestrictions{ID}`                                                             | Per-purpose publisher restrictions                                                                        |
| `IABTCF_PublisherConsent` / `IABTCF_PublisherLegitimateInterests`                              | Publisher TC                                                                                              |
| `IABTCF_PublisherCustomPurposesConsents` / `IABTCF_PublisherCustomPurposesLegitimateInterests` | Publisher custom purposes bit strings                                                                     |

**Google**

| Key                                  | Content                                                                                                                  |
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------ |
| `IABTCF_AddtlConsent`                | Google Additional Consent string                                                                                         |
| `IABTCF_EnableAdvertiserConsentMode` | `1` when the configuration leaves the ad signals to Google ([Firebase Analytics](#firebase-analytics)), absent otherwise |

**GPP and CCPA / USP**

| Key                         | Content                                                                                                                                                 |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `IABGPP_HDR_GppString`      | Full GPP string                                                                                                                                         |
| `IABGPP_GppSID`             | Section IDs in force, joined by `_` (for example `2`, or `2_6_7`)                                                                                       |
| `IABGPP_HDR_Version`        | GPP header version: `1`, or `0` while the GPP string is empty                                                                                           |
| `IABGPP_HDR_Sections`       | Section IDs present in the string, joined by `_`                                                                                                        |
| `IABGPP_[SectionID]_String` | One key per section present: `IABGPP_2_String` (TCF EU v2, the TC string itself), `IABGPP_6_String` (US Privacy), `IABGPP_7_String` (US National)       |
| `IABUSPrivacy_String`       | US Privacy string, for example `1YN-`. **Absent until a US choice is recorded** — saved from the consent UI's US screen, or passed to `setCcpaOptOut()` |

Sections 6 and 7 are present only when US Privacy applies to the user and the configuration does not disable it; section 6 also needs a recorded US choice.

**When the keys appear.** During `initialize()`: `IABTCF_CmpSdkID`, `IABTCF_CmpSdkVersion` and `IABTCF_PolicyVersion`. Once the configuration has loaded, before any choice: `IABTCF_gdprApplies`, `IABTCF_PolicyVersion` again (from the loaded Global Vendor List), `IABTCF_PublisherCC`, `IABTCF_PurposeOneTreatment`, `IABTCF_UseNonStandardTexts`, the publisher bit strings, which then hold the SDK's defaults rather than a choice, `IABTCF_EnableAdvertiserConsentMode` when it applies, and, for a US user, the GPP keys with the US National section. Only once a choice is recorded: the TC string, the purpose and vendor bit strings, the special feature opt-ins, the publisher restrictions, `IABTCF_AddtlConsent`, and the GPP keys for the other users. `IABUSPrivacy_String` waits for a US choice. To know whether a choice is recorded, test `IABTCF_TCString`.

## The in-app command API

Vendor SDKs that prefer the command interface to raw storage can call the in-app TCF API — the `__tcfapi` equivalent:

```kotlin
// Android
SirDataCMPApi.tcfApi().execute("ping", 2, { data, _, success -> /* … */ })
SirDataCMPApi.tcfApi().execute("getInAppTCData", 2, { data, _, success -> /* … */ })
SirDataCMPApi.tcfApi().execute("addEventListener", 2, { data, listenerId, success -> /* … */ })
```

```swift
// iOS
IosCMPApi.shared.tcfApi().execute(command: "ping", version: 2, callback: { data, _, success in /* … */ }, parameter: nil)
```

A listener registered with `addEventListener` is called back when the CMP has loaded, when the consent UI is shown, when a choice is recorded and when the UI closes. The second argument of every call is the listener ID, to pass to `removeEventListener`.

**In 2.0.0 these calls carry no `eventStatus`.** The first argument is the current TC data once the CMP has loaded, and the event name before that; the call made right after registering carries `"tcloaded"`. A listener therefore cannot tell `tcloaded`, `cmpuishown`, `useractioncomplete` and `cmpuiclosed` apart: on every call, read the current values — the stored `IABTCF_` keys, or `getInAppTCData` — rather than relying on which event fired.

Equivalent `gppApi()` and `uspApi()` command interfaces exist; GPP listener events do carry their name, in `eventName`. When the configuration leaves the ad signals to Google, `ping` and `getTCData` also carry `enableAdvertiserConsentMode: true`, as on the web. Command lists: [Reference — Android](/en/mobile-apps-native-sdk/mobile-sdk/reference.md#iab-api-access) and [Reference — iOS](/en/mobile-apps-native-sdk/mobile-sdk/reference-1.md#iab-api-access).

## Consent adapters

Each time a choice is recorded — from the consent UI, or from `acceptAll()`, `rejectAll()`, `saveConsent()` or `submitConsent()` — the SDK maps the TCF choice to the Google consent signals (`ad_storage`, `analytics_storage`, `ad_user_data`, `ad_personalization`) and forwards them to every adapter whose SDK is in the app. There is nothing to register and nothing to import: an adapter is active as soon as its SDK is linked.

| Adapter            | What it sends                                                                                              |
| ------------------ | ---------------------------------------------------------------------------------------------------------- |
| Firebase Analytics | Consent Mode v2 (`setConsent`) — see [Firebase Analytics](#firebase-analytics)                             |
| AppsFlyer          | `setConsentData`: GDPR applicability, data usage, ads personalization, ad storage                          |
| Adjust             | Measurement consent (`ad_storage`), and the Google DMA signals as `google_dma` third-party sharing options |
| Branch             | Tracking disabled while `ad_storage` is refused, and the Google DMA signals (`setDMAParamsForEEA`)         |

* **Forwarding happens when a choice is recorded, not at launch.** Firebase, Adjust and Branch keep what they were last given from one session to the next. AppsFlyer keeps it for the session only — see [AppsFlyer](#appsflyer).
* **Check what was forwarded** with `getRegisteredAdapters()` (the adapters whose SDK is in the app) and `getLastForwardedConsentState()`.
* **An adapter that fails says so** in the log — Logcat tag `SirDataCMP` on Android, `[CMP]` in the Xcode console on iOS — for example `Consent adapter 'Branch' failed to forward consent: …`. It never affects the other adapters, nor your app.
* **Android release builds:** the SDK ships the R8 rules its adapters need. There is nothing to add to your `proguard-rules.pro`.
* **iOS:** the adapters run inside the SDK, through the Objective-C runtime. They work the same with Swift Package Manager and CocoaPods, and whether or not you use the bundled consent UI. An adapter is active when its SDK's classes are linked into the app — for Firebase Analytics through Swift Package Manager, that is the `-ObjC` linker flag Firebase already requires.

## Firebase Analytics

Nothing to code. Do not call `setConsent` yourself for the signals the adapter sends: a value set by the app overrides the one Firebase derives from the TC string.

What the adapter sends depends on the configuration option **Enable TCF/Google Consent Mode Interoperability**, in the [Sirdata Console](https://cmp.sirdata.io):

* **Off** (the default): all four signals, `analytics_storage`, `ad_storage`, `ad_user_data` and `ad_personalization`, on every choice.
* **On**: `analytics_storage` only. The SDK writes `IABTCF_EnableAdvertiserConsentMode` = `1`, and Firebase derives the three ad signals from the TC string itself. This is what the web CMP does with the same option.

  When Firebase reads the TC string differs by platform. On Android it reads it again as soon as it changes. On iOS it reads it when the app starts and when it returns to the foreground: under this option, a new choice — a refusal included — reaches the three ad signals only then. With the option off, all four signals are set at once on both platforms: if a refusal must reach Firebase immediately on iOS too, leave the option off.

To see it on Android, enable Firebase's verbose logs (`adb shell setprop log.tag.FA VERBOSE`) and record a choice: under the option, Firebase logs `Consent generated from Tcf`, then events go out with `consent_signals: G111` after an "accept all" (`G1` + ad storage + analytics storage, `1` granted, `0` denied).

## AppsFlyer

Tell AppsFlyer to read the IAB keys, before `start()`:

```kotlin
// Android
AppsFlyerLib.getInstance().enableTCFDataCollection(true)
```

```swift
// iOS
AppsFlyerLib.shared().enableTCFDataCollection(true)
```

AppsFlyer then sends the stored TC string with its requests (`consent_data.tcf`). On a first launch its launch request leaves before any choice, with empty keys, and on Android it does not read them again during the session: the adapter covers that gap by passing each choice with `setConsentData` (`consent_data.manual`), which the next requests carry.

## Adjust and Branch

Neither reads the IAB keys; the adapters pass each choice. Nothing to code.

On Android, initialize Branch as usual (`Branch.getAutoInstance(context)` in `Application.onCreate()`): until then Branch has no instance to receive the consent, and the adapter logs that Branch is not initialized yet.

## SDKs that do not read the IAB keys

Gate them yourself on the purposes or vendors they depend on, at startup and on every change. The example below gates Meta on purpose 1 (storing or accessing information on a device). Disable the SDK's automatic start first: `com.facebook.sdk.AutoInitEnabled`, `com.facebook.sdk.AutoLogAppEventsEnabled` and `com.facebook.sdk.AdvertiserIDCollectionEnabled` set to `false` in the manifest, each as `<meta-data android:name="…" android:value="false"/>`.

`getConsentUpdates()` is a signal, not a list of decisions: it changes on every change to the selections, including each toggle the user moves inside the consent UI before saving, as well as on each recorded choice. Read the stored values each time it fires, as below — `hasPurposeConsent` reads storage, so an unsaved toggle does not change its answer. Call it after `initialize`.

A US choice — saved on the US screen, or recorded with `setCcpaOptOut` — does not change the counter. If you gate an SDK on the US signals, re-read `getUspString()` and `getGppString()` when the consent UI closes.

```kotlin
// Android — getConsentUpdates() emits its current value, then on every selection change
lifecycleScope.launch {
    SirDataCMPApi.getConsentUpdates().collect {
        val allowed = SirDataCMPApi.hasPurposeConsent(1)
        if (allowed) {
            FacebookSdk.setAutoInitEnabled(true)
            FacebookSdk.fullyInitialize()
        }
        FacebookSdk.setAdvertiserIDCollectionEnabled(allowed)
        FacebookSdk.setAutoLogAppEventsEnabled(allowed)
    }
}
```

```swift
// iOS — the watcher fires on each change, not at startup: apply once first
func applyGate() {
    let allowed = IosCMPApi.shared.hasPurposeConsent(purposeId: 1)
    // enable or disable the SDK here
}
applyGate()
let watcher = UpdateCounterWatcher(flow: IosCMPApi.shared.getConsentUpdates())
watcher.watch { _ in applyGate() }
// Keep `watcher` alive while you need it, and call watcher.close() when done.
```

## Google integrations

* **Additional Consent (AC string)** — maintained in `IABTCF_AddtlConsent` for Google ad vendors not registered in the TCF Global Vendor List. Read it with `getAddtlConsent()`.
* **Consent Mode** — the SDK derives the Google consent state from TCF purposes and forwards it to Firebase Analytics, see above. Inspect it with `getGoogleConsentState()`.
* **CCPA opt-out** — `setCcpaOptOut(true)` / `isCcpaOptedOut()` drive the USP string and the GPP US sections for "Do Not Sell" flows. Call `setCcpaOptOut` from a control the user operates: it records the user's own choice. The consent UI's US screen calls it when the user saves.

## WebViews

For consent-dependent web content inside a WebView, pass the TC string to the page and expose it through the page's `__tcfapi` stub, or append it to the URL if your web property accepts it:

```kotlin
val url = "https://example.com/article?gdpr=1&gdpr_consent=${SirDataCMPApi.getTcString()}"
```

## Checklist

1. Initialize Sirdata CMP **before** every ad and analytics SDK.
2. Collect consent **before** loading the first ad.
3. IAB-compliant SDKs, Firebase Analytics, Adjust and Branch: nothing else.
4. AppsFlyer: `enableTCFDataCollection(true)` before `start()`.
5. Other SDKs: gate them on `hasPurposeConsent(...)` / `hasVendorConsent(...)`, and re-evaluate on `getConsentUpdates()` — and, for the US signals, when the consent UI closes.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://cmp.docs.sirdata.net/en/mobile-apps-native-sdk/mobile-sdk/sharing-consent.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
