GDPR and CCPA
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.
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. You can learn more about GDPR and CCPA here.
Step 1. Update Privacy Policy
1.1 Make Sure Your Privacy Policy Includes Information About Advertising ID Collection.
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".
1.2 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's are two useful resources that you can utilize while working on your app compliance:
- App privacy details on the App Store
- 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.
- Swift
- Objective-C
@UIApplicationMain
final class MyAppDelegate: UIResponder, UIApplicationDelegate, AppodealInitializationDelegate {
func application(
_ application: UIApplication, didFinishLaunchingWithOptions
launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil ) -> Bool {
Appodeal.setAutocache(false, types: .interstitial)
Appodeal.setLogLevel(.verbose)
// New optional delegate for initialization completion
Appodeal.setInitializationDelegate(self)
/// Any other pre-initialization
/// app specific logic
Appodeal.initialize(
withApiKey: "APP_KEY",
types: .interstitial
)
return true
}
func appodealSDKDidInitialize() {
// Appodeal SDK did complete initialization
}
}
@interface MyAppDelegate ()
<AppodealInitializationDelegate>
@end
@implementation MyAppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[Appodeal setAutocache:NO types:AppodealAdTypeInterstitial];
[Appodeal setLogLevel:APDLogLevelVerbose];
// New optional delegate for initialization completion
[Appodeal setInitializationDelegate:self];
/// Any other pre-initialization
/// app specific logic
[Appodeal initializeWithApiKey:@"APP KEY" types:AppodealAdTypeInterstitial];
return YES;
}
- (void)appodealSDKDidInitialize {
// Appodeal SDK did complete initialization
}
@end
Advanced
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.
You can still use your own Consent Manager or customize ours by following the steps below.
Update User Consent
If you want the user to be able to change their mind about the value of Consent, then you can update it using the method to process and pass the user's consent.
These methods should be called before Appodeal SDK initialization. The Consent value cannot be changed at runtime. As an option, you can save the user decision to UserDefaults and update the consent decision while the next app launch.
- Swift
- Objective-C
Appodeal.updateUserConsentGDPR(.personalized)
Appodeal.updateUserConsentCCPA(.optIn)
[Appodeal updateUserConsentGDPR:APDGDPRUserConsentPersonalized];
[Appodeal updateUserConsentCCPA:APDCCPAUserConsentOptIn];
APDGDPRUserConsentUnknown - the status is unknown. The consent window wasn't shown.
APDGDPRUserConsentPersonalized - the user has granted consent for personalized ads.
APDGDPRUserConsentNonPersonalized - the user has NOT granted consent for personalized ads.
APDCCPAUserConsentOptIn- the user has granted consent for personalized ads.
APDCCPAUserConsentOptOut - the user has NOT granted consent for personalized ads.
You can use these methods to provide the GDPR/CCPA user consent for ad networks in Appodeal SDK. You can read more info about GDPR/CCPA here.
Manage Stack Consent
Stack Consent Manager is included in Appodeal SDK by default. Consent will be requested automatically on SDK initialization, and consent form will shown if it is necessary without any additional calls.
If you wish, you can manage and update consent manually using Stack Consent Manager calls.
Synchronize Consent Manager
Consent manager SDK can be synchronized at any moment of application lifecycle. We recommend to synchronize it at application launch. Multiple synchronization calls are allowed.
Import StackConsentManager/StackConsentManager.h
in AppDelegate.m
- Swift
- Objective-C
import StackConsentManager
#import <StackConsentManager/StackConsentManager.h>
Call synchronise consent in -application:didFinishLaunchingWithOptions:
- Swift
- Objective-C
/// Initialisation
class YourAppDelegate: AppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]?
) -> Bool {
STKConsentManager.shared().synchronize(withAppKey: "Your app key") { [unowned self] error in
error.map { print("Error while synchronising consent manager: \($0)") }
guard STKConsentManager.shared().shouldShowConsentDialog == .true else {
// Initialise SDK here
return
}
// Load and present consent dialog
}
return true
}
}
/// Initialisation
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
__weak typeof(self) weakSelf = self;
[STKConsentManager.sharedManager synchronizeWithAppKey:APP_KEY completion:^(NSError *error) {
__strong typeof(self) strongSelf = weakSelf;
if (error) {
NSLog(@"Error while synchronising consent manager: %@", error);
}
if (STKConsentManager.sharedManager.shouldShowConsentDialog != STKConsentBoolTrue) {
// Initialise SDK here
return ;
}
// Load and present consent dialog
}];
return YES;
}
APP_KEY
is required parameter (Appodeal APP Key)
completion
is block that invokes after synchronization
After synchronization completion, you can receive information about the previous user consent and regulation zone.
Before synchronization these parameters are undefined
- Swift
- Objective-C
// Check regulation
let regulation = STKConsentManager.shared().regulation
// Check consent status
let status = STKConsentManager.shared().consentStatus
// Available after first show (synchronisation is required)
let consentString = STKConsentManager.shared().iabConsentString
// Check regulation
STKConsentRegulation *regulation = [STKConsentManager.sharedManager regulation];
// Check consent status
STKConsentStatus *status = [STKConsentManager.sharedManager consentStatus];
// Available after first show (synchronisation is required)
NSString *consentString = [STKConsentManager.sharedManager iabConsentString];
SDK only allows calling consent window api after synchronization
After SDK has been synchronized, you can load the consent window. Loading is allowed in any regulation zone and independent from previous consent.
- Swift
- Objective-C
// Load and present consent dialog
STKConsentManager.shared().loadConsentDialog { [unowned self] error in
error.map { print("Error while loading consent dialog: \($0)") }
guard
let controller = UIApplication.shared.keyWindow?.rootViewController,
STKConsentManager.shared().isConsentDialogReady
else {
// Initialise SDK here
return
}
STKConsentManager.shared().showConsentDialog(fromRootViewController: controller,
delegate: self)
}
// Load and present consent dialog
__weak typeof(self) weakSelf = self;
[STKConsentManager.sharedManager loadConsentDialog:^(NSError *error) {
__strong typeof(self) strongSelf = weakSelf;
if (error) {
NSLog(@"Error while loading consent dialog: %@", error);
}
if (!STKConsentManager.sharedManager.isConsentDialogReady) {
// Initialise SDK here
return ;
}
UIViewController *rootViewController = [UIApplication.sharedApplication keyWindow].rootViewController;
[STKConsentManager.sharedManager showConsentDialogFromRootViewController:rootViewController
delegate:strongSelf];
}];
Show Consent Window
You can check if the consent window is ready.
- Swift
- Objective-C
// Indicates that consent window ready to present
let isReady = STKConsentManager.shared().isConsentDialogReady
// Indicates that consent window ready to present
BOOL isReady = [STKConsentManager.sharedManager isConsentDialogReady];
When the consent window Is ready you can present it.
- Swift
- Objective-C
// Show consent dialog
STKConsentManager.shared().showConsentDialog(fromRootViewController: controller, delegate: self)
// Show consent dialog
[STKConsentManager.sharedManager showConsentDialogFromRootViewController:rootViewController delegate:strongSelf];
Handling presentation callbacks.
- Swift
- Objective-C
/// Get consent manager presentation callbacks
class YourViewController: UIViewController, STKConsentManagerDisplayDelegate {
// MARK: STKConsentManagerDisplayDelegate
func consentManagerWillShowDialog(_ consentManager: STKConsentManager) {}
func consentManager(_ consentManager: STKConsentManager, didFailToPresent error: Error) {
// Something went wrong
initializeAppodealSDK()
}
func consentManagerDidDismissDialog(_ consentManager: STKConsentManager) {
// Resume app
initializeAppodealSDK()
}
}
/// Get consent manager presentation callbacks
@interface YourViewController() <STKConsentManagerDisplayDelegate>
@end
@implementation YourViewController
// MARK: STKConsentManagerDisplayDelegate
- (void)consentManagerWillShowDialog:(STKConsentManager *)consentManager {}
- (void)consentManagerDidDismissDialog:(STKConsentManager *)consentManager {
// Something went wrong
[self initializeAppodealSDK];
}
- (void)consentManager:(STKConsentManager *)consentManager didFailToPresent:(NSError *)error {
// Resume app
[self initializeAppodealSDK];
}
@end
Update Consent Status
Publishers need to pass the Consent result object from Stack Consent Manager SDK to the Appodeal.updateConsentReport(report)
method before SDK initialization.
- Swift
- Objective-C
let report = STKConsentManager.shared().consent!
Appodeal.updateConsentReport(report)
Appodeal.initialize(withApiKey: appKey, types: adTypes)
id<STKConsent> report = STKConsentManager.sharedManager.consent;
[Appodeal updateConsentReport:report];
[Appodeal initializeWithApiKey:APP_KEY types:types];
Advanced
You can force consent manager to write iAB keys in NSUserDefaults
by setting up storage property
before synchronization to STKConsentDialogStorageUserDefaults
- Swift
- Objective-C
// Store IAB strings in user defaults
// should be called before synchronize
STKConsentManager.shared().storage = .userDefaults
// Store IAB strings in user defaults
// should be called before synchronize
STKConsentManager.sharedManager.storage = STKConsentDialogStorageUserDefaults;
You can register yourself as a vendor before synchronization.
Parameter | Type | Description |
---|---|---|
id | Integer | iAB id. If you are not registered as iAB vendor you can use custom id |
name | String | Native ad Display name. Will be displayed in the consent window |
purposesIds | Array of integersring | iAB purposes ids array |
featuresIds | Array of integers | iAB features ids array |
legIntPurposeIds | Array of integers | iAB leg int purposes ids array |
- Swift
- Objective-C
// Register custom vendor (will be displayed on consent window)
// should be called before synchronize
STKConsentManager.shared().registerCustomVendor { builder in
let _ = builder
.appendPolicyURL(URL(string: "https://cmg.com/privacy")!)
.appendName("My app")
.appendBundle("com.app.bundle")
.appendPurposesIds([1, 2, 3])
.appendFeaturesIds([5, 6])
.appendLegIntPurposeIds([1])
}
// Register custom vendor (will be displayed on consent window)
// should be called before synchronize
[STKConsentManager.sharedManager registerCustomVendor:^(STKVendorBuilder * _Nonnull builder) {
builder
.appendPolicyURL([NSURL URLWithString:@"https://cmg.com/privacy"])
.appendName(@"My app")
.appendBundle(@"com.app.bundle")
.appendPurposesIds(@[@1, @2, @3])
.appendFeaturesIds(@[@5, @6])
.appendLegIntPurposeIds(@[@1]);
}];
Example
- Swift
- Objective-C
import UIKit
import Appodeal
import StackConsentManager
@main
class AppDelegate: UIResponder, UIApplicationDelegate, AppodealInitializationDelegate, STKConsentManagerDisplayDelegate {
let appKey = "Your app key"
let adTypes:AppodealAdType = .banner
func appodealSDKDidInitialize() {}
func consentManagerWillShowDialog(_ consentManager: STKConsentManager) {
Appodeal.initialize(withApiKey: appKey, types: adTypes)
}
func consentManager(_ consentManager: STKConsentManager, didFailToPresent error: Error) {
Appodeal.initialize(withApiKey: appKey, types: adTypes)
}
func consentManagerDidDismissDialog(_ consentManager: STKConsentManager) {
let report = STKConsentManager.shared().consent!
Appodeal.updateConsentReport(report)
Appodeal.initialize(withApiKey: appKey, types: adTypes)
}
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
Appodeal.setInitializationDelegate(self)
// Synchronize Consent manager
STKConsentManager.shared().synchronize(withAppKey: appKey) { [unowned self] error in
error.map { print("Error while synchronising consent manager: \($0)") }
guard STKConsentManager.shared().shouldShowConsentDialog == .true else {
Appodeal.initialize(withApiKey: appKey, types: adTypes)
return
}
// Load and present consent dialog
STKConsentManager.shared().loadConsentDialog { [unowned self] error in
error.map { print("Error while loading consent dialog: \($0)") }
guard
let controller = UIApplication.shared.keyWindow?.rootViewController,
STKConsentManager.shared().isConsentDialogReady
else {
Appodeal.initialize(withApiKey: appKey, types: adTypes)
return
}
// Show consent dialog
STKConsentManager.shared().showConsentDialog(fromRootViewController: controller,
delegate: self)
}
}
return true
}
// ...
}
#import "AppDelegate.h"
#import <Appodeal/Appodeal.h>
#import <StackConsentManager/StackConsentManager.h>
@interface AppDelegate ()
<AppodealInitializationDelegate, STKConsentManagerDisplayDelegate>
@property (strong, nonatomic) NSString *appKey;
@property (assign, nonatomic) AppodealAdType adTypes;
@end
@implementation AppDelegate
- (void)appodealSDKDidInitialize {}
- (void)consentManagerWillShowDialog:(STKConsentManager *)consentManager {}
- (void)consentManagerDidDismissDialog:(STKConsentManager *)consentManager {
// Resume app
[Appodeal updateConsentReport:[STKConsentManager sharedManager].consent];
[Appodeal initializeWithApiKey:_appKey types:_adTypes];
}
- (void)consentManager:(STKConsentManager *)consentManager didFailToPresent:(NSError *)error {
// Something went wrong
[Appodeal initializeWithApiKey:_appKey types:_adTypes];
}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
__weak typeof(self) weakSelf = self;
[Appodeal setInitializationDelegate:self];
self.appKey = @"Your app key";
self.adTypes = AppodealAdTypeBanner;
[STKConsentManager.sharedManager synchronizeWithAppKey:_appKey completion:^(NSError *error) {
if (error) {
NSLog(@"Error while synchronising consent manager: %@", error);
}
if (STKConsentManager.sharedManager.shouldShowConsentDialog != STKConsentBoolTrue) {
// Initialise SDK here
[Appodeal initializeWithApiKey:_appKey types:_adTypes];
return;
}
// Load and present consent dialog
[STKConsentManager.sharedManager loadConsentDialog:^(NSError *error) {
__strong typeof(self) strongSelf = weakSelf;
if (error) {
NSLog(@"Error while loading consent dialog: %@", error);
}
if (!STKConsentManager.sharedManager.isConsentDialogReady) {
[Appodeal initializeWithApiKey:_appKey types:_adTypes];
return ;
}
// Show consent dialog
UIViewController *rootViewController = [UIApplication.sharedApplication keyWindow].rootViewController;
[STKConsentManager.sharedManager showConsentDialogFromRootViewController:rootViewController
delegate:strongSelf];
}];
}];
return YES;
}
Custom Consent Window
If you don't want to use the Stack Content Manager SDK, you can use another solution to get user's consent and provide it before Appodeal SDK initialization:
Publishers need to pass the consent before the initialize method of our SDK using the following methods.
- Swift
- Objective-C
Appodeal.updateUserConsentGDPR(.personalized)
Appodeal.updateUserConsentCCPA(.optIn)
[Appodeal updateUserConsentGDPR:APDGDPRUserConsentPersonalized];
[Appodeal updateUserConsentCCPA:APDCCPAUserConsentOptIn];
APDGDPRUserConsentUnknown - the status is unknown. The consent window wasn't shown.
APDGDPRUserConsentPersonalized- the user has granted consent for personalized ads.
APDGDPRUserConsentNonPersonalized - the user has NOT granted consent for personalized ads.
APDCCPAUserConsentOptIn - the user has granted consent for personalized ads.
APDCCPAUserConsentOptOut - the user has NOT granted consent for personalized ads.
Providing consent values before initialization prevents the build-in showing of the Stack Consent Window. You should use this approach to disable the consent window shown in GDPR/CCPA zones or if you want the user to be able to change their mind about the value of Consent.