GDPR & CCPA
Keep in mind that it’s best to contact qualified legal professionals, if you haven’t done so already, to get more information and be well-prepared for compliance.
The General Data Protection Regulation, better known as GDPR, took effect on May 25, 2018. It’s a set of rules designed to give EU citizens more control over their personal data. Any businesses established in the EU or with users based in Europe are required to comply with GDPR or risk facing heavy fines. The California Consumer Privacy Act (CCPA) went into effect on January 1, 2020. We have put together some guidelines to help publishers understand better the steps they need to take to be GDPR compliant.
Step 1. Update Privacy Policy
Include Additional Information To Your Privacy Policy
Don’t forget to add information about IP address and advertising ID collection, as well as the link to Appodeal’s privacy policy to your app’s privacy policy on the App Store.
To speed up the process, you could use privacy policy generators - just insert advertising ID, IP address, and location (if you collect users’ location) in the Personally Identifiable Information you collect field (in line with other information about your app) and the link to Appodeal’s privacy policy in the Link to the privacy policy of third party service providers used by the app field.
Add A Privacy Policy To Your Mobile App
You must add your explicit privacy policies in two places: on your app’s Store Listing page and within your app.
You can find detailed instructions on adding your privacy policy to your app on legal service websites. For example, Iubenda, the solution tailored to legal compliance, provides a comprehensive guide on including a privacy policy in your app.
Make sure that your privacy policy website has an SSL certificate—this point might seem obvious, but it’s still essential.
Here are two useful resources that you can utilize while working on your app compliance:
- Privacy, Security and Deception regulations (by Google Play)
- Recommendations on Developing a Meaningful Privacy Policy (by Attorney General California Department of Justice)
Please note that although we’re always eager to back you up with valuable information, we’re not authorized to provide any legal advice. It’s important to address your questions to lawyers who specialize in this area.
Step 2. Stack Consent Manager
In order for Appodeal and our ad providers to deliver ads that are more relevant to your users, as a mobile app publisher, you need to collect explicit user consent in the regions covered by GDPR and CCPA.
To get consent for collecting personal data of your users, we suggest you use a ready-made solution - Stack Consent Manager.
Stack Consent Manager comes with a pre-made consent window that you can easily present to your users. That means you no longer need to create your own consent window.
Consent will be requested automatically on SDK initialization, and consent form will be shown if it is necessary without any additional calls.
Please keep in mind that Consent will be shown only in the EU and California regions, you can use VPN for testing.
- UPM Distribution
- Manual Distribution
private void Start()
{
int adTypes = AppodealAdType.Interstitial | AppodealAdType.Banner | AppodealAdType.RewardedVideo | AppodealAdType.Mrec;
string appKey = "YOUR_APPODEAL_APP_KEY";
AppodealCallbacks.Sdk.OnInitialized += OnInitializationFinished;
Appodeal.Initialize(appKey, adTypes);
}
#region Initialization Callback
public void OnInitializationFinished(object sender, SdkInitializedEventArgs e) { }
#endregion
class Test : IAppodealInitializationListener
{
private void Start()
{
int adTypes = Appodeal.INTERSTITIAL | Appodeal.BANNER | Appodeal.REWARDED_VIDEO | Appodeal.MREC;
string appKey = "YOUR_APPODEAL_APP_KEY";
Appodeal.initialize(appKey, adTypes, this);
}
#region Initialization Callback
public void onInitializationFinished(List<string> errors) { }
#endregion
}
Advanced
Custom Consent Logic
Stack Consent Manager is included in Appodeal SDK by default. Consent will be requested automatically on SDK initialization, and consent form will be shown if it is necessary without any additional calls.
If you would like to customize consent managing logic, call the Appodeal update consent method before SDK initialization and pass the value of your received consent.
- UPM Distribution
- Manual Distribution
private void Init()
{
// Either
Appodeal.UpdateConsent(consentObj);
// OR
Appodeal.UpdateConsentGdpr(GdprUserConsent.Personalized);
// OR
Appodeal.UpdateConsentCcpa(CcpaUserConsent.OptIn);
Appodeal.Initialize(appKey, adTypes);
}
private void Init()
{
// Either
Appodeal.updateConsent(consentObj);
// OR
Appodeal.updateConsentGdpr(GdprUserConsent.Personalized);
// OR
Appodeal.updateConsentCcpa(CcpaUserConsent.OptIn);
Appodeal.initialize(appKey, adTypes, this);
}
You can use this method to provide the GDPR/CCPA user consent for ad networks in Appodeal SDK anywhere in your application.
Manage Consent Manually
If you wish, you can manage and update consent manually using Stack Consent Manager calls.
Update Consent Status
- UPM Distribution
- Manual Distribution
Call RequestConsentInfoUpdate()
method on an instance of ConsentManager
class:
private ConsentManager _consentManager;
_consentManager = ConsentManager.GetInstance();
_consentManager?.RequestConsentInfoUpdate(AppKey, this);
Subscribe to the desired Consent Manager event using one of the options from this guide.
You can use callbacks as shown below:
public void SomeMethod()
{
ConsentManagerCallbacks.ConsentInfo.OnUpdated += OnConsentInfoUpdated;
ConsentManagerCallbacks.ConsentInfo.OnFailedToUpdate += OnFailedToUpdateConsentInfo;
}
#region ConsentInfo Callbacks
private void OnConsentInfoUpdated(object sender, ConsentEventArgs e)
{
Debug.Log("Consent Info Updated");
}
private void OnFailedToUpdateConsentInfo(object sender, ConsentManagerExceptionEventArgs e)
{
Debug.Log("Failed to update Consent Info");
}
#endregion
All returned errors are Exception instances with custom codes:
Code | Description |
---|---|
INTERNAL(1) | Error on the SDK side. Includes JS-bridge or encoding/decoding errors |
NETWORKING(2) | HTTP errors, parse request/response |
INCONSISTENT(3) | Incorrect SDK API usage |
Consent manager SDK can be synchronized at any moment of the application lifecycle. We recommend synchronizing it at the application launch. Multiple synchronization calls are allowed.
If the consent information is successfully updated, the updated consent
is provided via the
ConsentManagerCallbacks.ConsentInfo.OnUpdated
event callback.
Now you can receive information about the previous user consent and regulation zone. Before request these parameters
are undefined
.
_consentManager?.GetConsentStatus();
Consent Status | Definition |
---|---|
Unknown | The user has neither granted nor declined consent for personalized or non-personalized ads. |
NonPersonalized | The user has granted consent for non-personalized ads. |
PartlyPersonalized | The user has granted partly(for a few Ad networks) consent for personalized ads. |
Personalized | The user has granted consent for personalized ads. |
Call requestConsentInfoUpdate()
method on an instance of ConsentManager
class:
private ConsentManager consentManager;
consentManager = ConsentManager.getInstance();
consentManager?.requestConsentInfoUpdate(appKey, this);
Extend your class with IConsentInfoUpdateListener
interface:
SomeClassName : IConsentInfoUpdateListener { }
Now you can use use the following callback methods within your public class:
public void onConsentInfoUpdated(Consent consent)
{
Debug.Log("onConsentInfoUpdated");
}
public void onFailedToUpdateConsentInfo(ConsentManagerException error)
{
Debug.Log($"onFailedToUpdateConsentInfo Reason: {error.getReason()}");
}
All returned errors are Exception instances with custom codes:
Code | Description |
---|---|
INTERNAL(1) | Error on the SDK side. Includes JS-bridge or encoding/decoding errors |
NETWORKING(2) | HTTP errors, parse request/response |
INCONSISTENT(3) | Incorrect SDK API usage |
Consent manager SDK can be synchronized at any moment of the application lifecycle. We recommend synchronizing it at the application launch. Multiple synchronization calls are allowed.
If the consent information is successfully updated, the updated consent
is provided via the onConsentInfoUpdated()
method of the IConsentInfoUpdateListener
.
Now you can receive information about the previous user consent and regulation zone. Before request these parameters
are undefined
.
consentManager.getConsentStatus();
Consent Status | Definition |
---|---|
UNKNOWN | The user has neither granted nor declined consent for personalized or non-personalized ads. |
NON_PERSONALIZED | The user has granted consent for non-personalized ads. |
PARTLY_PERSONALIZED | The user has granted partly(for a few Ad networks) consent for personalized ads. |
PERSONALIZED | The user has granted consent for personalized ads. |
Necessity Of Showing The Consent Window
After the OnConsentInfoUpdated
callback method was called, you need to determine if your users are subject to the
GDPR and CCPA and whether you should show the consent window for the collection of personal data.
You can check whether to show a Consent Dialog or not. Before request these parameters are undefined
(Unknown status)
- UPM Distribution
- Manual Distribution
_consentManager?.ShouldShowConsentDialog()
ShouldShow | Definition |
---|---|
True | The user is within the scope of the GDPR or CCPA laws, the consent window should be displayed. |
False | The user is not within the scope of the GDPR or CCPA laws OR the consent window has already been shown. |
Unknown | The value is undefined (the RequestConsentInfoUpdate() method was not called). |
consentManager.shouldShowConsentDialog()
ShouldShow | Definition |
---|---|
TRUE | The user is within the scope of the GDPR or CCPA laws, the consent window should be displayed. |
FALSE | The user is not within the scope of the GDPR or CCPA laws OR the consent window has already been shown. |
UNKNOWN | The value is undefined (the requestConsentInfoUpdate() method was not called). |
Show Consent Window
After the SDK requests, you can build and load the consent window. Loading allowed in any regulation zone and independent from previous consent.
- UPM Distribution
- Manual Distribution
private ConsentForm _consentForm;
_consentForm = ConsentForm.GetInstance(this);
_consentForm?.Load();
You can check that the consent window is ready or not
bool loaded = _consentForm.IsLoaded();
After the consent window is ready you can show it
_consentForm?.Show();
After the first display of the consent window, the ShouldShowConsentDialog()
method will return the value of
ConsentShouldShow.False
in the next sessions.
private ConsentForm consentForm;
consentForm = ConsentForm.getInstance(this);
consentForm?.load();
You can check that the consent window is ready or not
bool loaded = consentForm.isLoaded();
After the consent window is ready you can show it
consentForm.show();
After the first display of the consent window, the shouldShowConsentDialog()
method will return the value of
Consent.ShouldShow.FALSE
in the next sessions.
Handling Presentation Callbacks
- UPM Distribution
- Manual Distribution
You can use callbacks as shown below:
public void SomeMethod()
{
ConsentManagerCallbacks.ConsentForm.OnLoaded += OnConsentFormLoaded;
ConsentManagerCallbacks.ConsentForm.OnExceptionOccurred += OnConsentFormExceptionOccurred;
ConsentManagerCallbacks.ConsentForm.OnOpened += OnConsentFormOpened;
ConsentManagerCallbacks.ConsentForm.OnClosed += OnConsentFormClosed;
}
#region ConsentForm Callbacks
private void OnConsentFormLoaded(object sender, EventArgs e)
{
Debug.Log("Consent Form loaded");
}
private void OnConsentFormExceptionOccurred(object sender, ConsentManagerExceptionEventArgs e)
{
Debug.Log("Consent Form Exception Occurred");
}
private void OnConsentFormOpened(object sender, EventArgs e)
{
Debug.Log("Consent Form Opened");
}
private void OnConsentFormClosed(object sender, ConsentEventArgs e)
{
Debug.Log("Consent Form Closed");
}
#endregion
- Extend your class with IConsentFormListener:
SomeClassName : IConsentFormListener {}
- Implement required callback methods:
public void onConsentFormLoaded()
{
Debug.Log("ConsentFormListener - onConsentFormLoaded");
}
public void onConsentFormError(ConsentManagerException exception)
{
Debug.Log($"ConsentFormListener - onConsentFormError, reason - {exception.getReason()}");
}
public void onConsentFormOpened()
{
Debug.Log("ConsentFormListener - onConsentFormOpened");
}
public void onConsentFormClosed(Consent consent)
{
Debug.Log($"ConsentFormListener - onConsentFormClosed, consentStatus - {consent.getStatus()}");
}
Advanced
- UPM Distribution
- Manual Distribution
You can force consent manager to write iAB keys in SharedPreference
by setting up storage property before
the request to ConsentManagerStorage
.
_consentManager = ConsentManager.GetInstance();
_consentManager.SetStorage(ConsentManagerStorage.SharedPreference);
SharedPreference
and only overrides them.You can register yourself as a vendor before request. Should be called before request.
_consentManager = ConsentManager.GetInstance();
var customVendor = new Vendor.Builder(
"Appodeal Test",
"com.appodeal.test",
"https://customvendor.com")
.SetPurposeIds()
.SetFeatureIds()
.SetLegitimateInterestPurposeIds()
.Build();
_consentManager?.SetCustomVendor(customVendor);
var vendor = _consentManager?.GetCustomVendor("com.appodeal.test");
Parameter | Type | Description |
---|---|---|
name | String | Display name. Will be displayed in the consent window |
bundle | String | Custom string to check consent result for the vendor |
policyUrl | String | Policy URL |
purposeIds | Array of integers | iAB purposes ids array |
featureIds | Array of integers | iAB features ids array |
legitimateInterestPurposeIds | Array of integers | iAB leg int purposes ids array |
After consent form is closed you can check whether or not certain vendor has consent.
consent?.HasConsentForVendor("com.appodeal.test");
HasConsent | Definition |
---|---|
True | The vendor's consent value is true. |
False | The vendor's consent value is false. |
Unknown | The value is undefined (the RequestConsentInfoUpdate() method was not called). |
You can force consent manager to write iAB keys in SharedPreference
by setting up storage property before
the request to ConsentManager.Storage
.
consentManager = ConsentManager.getInstance();
consentManager.setStorage(ConsentManager.Storage.SHARED_PREFERENCE);
SharedPreference
and only overrides them.You can register yourself as a vendor before request. Should be called before request.
consentManager = ConsentManager.getInstance();
var customVendor = new Vendor.Builder(
"Appodeal Test",
"com.appodeal.test",
"https://customvendor.com")
.setPurposeIds()
.setFeatureIds()
.setLegitimateInterestPurposeIds()
.build();
consentManager.setCustomVendor(customVendor);
var vendor = consentManager.getCustomVendor("com.appodeal.test");
Parameter | Type | Description |
---|---|---|
name | String | Display name. Will be displayed in the consent window |
bundle | String | Custom string to check consent result for the vendor |
policyUrl | String | Policy URL |
purposeIds | Array of integers | iAB purposes ids array |
featureIds | Array of integers | iAB features ids array |
legitimateInterestPurposeIds | Array of integers | iAB leg int purposes ids array |
After consent form is closed you can check whether or not certain vendor has consent.
consent.hasConsentForVendor("com.appodeal.test");
HasConsent | Definition |
---|---|
TRUE | The vendor's consent value is true. |
FALSE | The vendor's consent value is false. |
UNKNOWN | The value is undefined (the RequestConsentInfoUpdate() method was not called). |