Privacy and Security

SDK privacy and security features helps you prevent data integrity issues and comply with user data privacy concerns. If you want to get user consent before tracking them then with Consents feature you can make SDK collect data only after your users give their consent. With SDKs tampering protection abilities you would also be able to mitigate man-in-the-middle (MitM) and ensure data integrity.

Web Android iOS Flutter Dart HarmonyOS React Native Windows Unity Java C++ API Call

User Consent

For consent to work, first you need to enable it during initialization, later you should provide or revoke consent. If this feature is enabled data tracking will only work when consent is given and as SDK does not store consent information it should be given after each init or user change.

Asynchronous Synchronous
// in your Countly init script
Countly.require_consent = true;
      
// to give consent {string|array}
Countly.q.push(['add_consent', feature]);

// to remove consent {string|array}
Countly.q.push(['remove_consent', feature]);
// during init
config.setRequiresConsent(true);
  
// give all consents for all features
config.giveAllConsent();

// give consents for the features which specified below
config.setConsentEnabled(String[] featureNames);
  
// give consent for all features
Countly.sharedInstance().consent().giveConsentAll(); // or singular version giveConsent(String[] featureNames)

// remove consent for all features
Countly.sharedInstance().consent().removeConsentAll(); // or singular version removeConsent(String[] featureNames)
Objective-C Swift
// enable during init
config.requiresConsent = YES;

// giving all consent during init
config.enableAllConsents = YES;

// giving partial consent during init
config.consents = @[CLYConsentSessions, CLYConsentEvents];

// giving consent after init
[Countly.sharedInstance giveAllConsents];
[Countly.sharedInstance giveConsentForFeatures:@[CLYConsentSessions, CLYConsentEvents];
[Countly.sharedInstance cancelConsentForFeatures:@[CLYConsentSessions, CLYConsentEvents];
[Countly.sharedInstance cancelConsentForAllFeatures];
// enable during init
config.setRequiresConsent(true);

// giving consent during init  
config.setConsentEnabled([CountlyConsent.location, CountlyConsent.sessions, CountlyConsent.attribution, CountlyConsent.push, CountlyConsent.events, CountlyConsent.views, CountlyConsent.crashes, CountlyConsent.users, CountlyConsent.push, CountlyConsent.starRating, CountlyConsent.apm, CountlyConsent.feedback, CountlyConsent.remoteConfig, CountlyConsent.content])

// giving all consent during init
config.giveAllConsents()
  
//give consent values after init
Countly.giveConsent([CountlyConsent.events, CountlyConsent.views, CountlyConsent.starRating, CountlyConsent.crashes]);

//remove consent values after init
Countly.removeConsent([CountlyConsent.events, CountlyConsent.views, CountlyConsent.starRating, CountlyConsent.crashes]);
  
//give consent to all features
Countly.giveAllConsent();

//remove consent from all features
Countly.removeAllConsent();
// during init sdk already requires consent
final config = CountlyConfig(
    appKey: 'APP_KEY',
    serverUrl: 'SERVER_URL',
    startWithUnknownConsent: true, // optional
);

final sdk = await Countly.init(config);

// give consent (for all)
await sdk.consents.giveConsent();

// revoke consent (for all)
await sdk.consents.revokeConsent();
// during init
config.consent.setRequiresConsent(true);
// optionally start in Unknown Consent Mode (collect locally, hold transport
// until the integrator resolves the unknown state)
config.consent.enableUnknownConsentMode();

// after init — runtime API is binary (all features at once)
const consent = Countly.sharedInstance().consent;
consent.giveConsentAll();
consent.removeConsentAll();
const allGranted: boolean = consent.checkAllConsent();
// enable during init
config.setRequiresConsent(true);

// giving consent during init      
config.giveConsent(["events", "views", "star-rating", "crashes"]);

// To add/remove consent for a single feature (string parameter)
Countly.giveConsent("events");
Countly.removeConsent("events");

// To add/remove consent for a subset of features (array of strings parameters)
Countly.giveConsent(["events", "views", "star-rating", "crashes"]);
Countly.removeConsent(["events", "views", "star-rating", "crashes"]);
  
// To add/remove consent for all available features
Countly.giveAllConsent();
Countly.removeAllConsent();
// enable during init
config.consentRequired = true;

// giving consent during init
Dictionary<ConsentFeatures, bool> consent = new Dictionary<ConsentFeatures, bool>();
consent.Add(ConsentFeatures.Crashes, true);
config.givenConsent = consent;

  
//preparing consent features
Dictionary<ConsentFeatures, bool> consent = new Dictionary<ConsentFeatures, bool>();
consent.Add(ConsentFeatures.Crashes, true);

//changing consent
Countly.Instance.SetConsent(consent);
// enable during init
config.SetRequiresConsent(true);

// giving consent during init
Consents[] consents = new Consents[] { Consents.Users, Consents.Location };
config.GiveConsent(consents);

// give consent to "X" feature
Countly.Instance.Consents.GiveConsent(new Consents[] { Consents.Sessions });

// remove consent from "X" feature
Countly.Instance.Consents.RemoveConsent(new Consents[] { Consents.Sessions });
  
// give consent to all features
Countly.Instance.Consent.GiveConsentAll();

// remove consent from all features
Countly.Instance.Consent.RemoveAllConsent();  
  

Not supported.

To send user consent changes to the server:

curl --request POST \
  --url 'https://YOUR_SERVER/i' \
  --header 'Accept: application/json' \
  --header 'Content-Type: application/x-www-form-urlencoded' \
  --data 'app_key=APP_KEY' \
  --data 'device_id=DEVICE_ID' \
  --data 'timestamp=TIMESTAMP' \
  --data 'hour=HOUR' \
  --data 'dow=DAYOFTHEWEEK' \
  --data 'consent={"events": true, "scrolls": false, "crashes": true}'

You should expect the response to be:

# 200
{
  "result": "Success"
}
# All possible consents:
sessions, events, views, scrolls, clicks, forms, crashes, attribution, users, feedback, 
apm, location, push, content, star-rating, remote-config starRating, remoteConfig
  
# All possible consents:
CLYConsentSessions, CLYConsentEvents, CLYConsentUserDetails, CLYConsentCrashReporting, CLYConsentPushNotifications,
CLYConsentLocation, CLYConsentViewTracking, CLYConsentAttribution, CLYConsentPerformanceMonitoring, CLYConsentFeedback,
CLYConsentRemoteConfig, CLYConsentContent
        

Parameter Tamper Protection

Countly can be configured to use a salt (from Management → Applications and inside the SDK) to add checksum to SDK requests in order to prevent parameter tampering.

Asynchronous Synchronous
// befor init
Countly.salt = "your_salt";     
config.setParameterTamperingProtectionSalt("your_salt");
Objective-C Swift
config.secretSalt = @"your_salt";
config.setParameterTamperingProtectionSalt("your_salt");

Not supported.

config.network.setParameterTamperingProtectionSalt('your_salt');
config.enableParameterTamperingProtection("your_salt");
config.SetParamaterTamperingProtectionSalt("your_salt");
config.SetParameterTamperingProtectionSalt("your_salt");
config.enableParameterTamperingProtection("your_salt");
cly::Countly.getInstance().setSalt("your_salt");

To enable it, you need to generate a salted checksum and attach it to each request. Follow these steps:

  1. Form a query string with your request (key=value pairs, joined by &). Do not URL-encode values yet. If any value is URL-encoded, decode them first.
  2. Append the salt to the end of the String value you have prepared. And calcuate sha256 hash of it.
  3. URL-encode the query string. After that append the calculated hash like this: &checksum256=CALCULATED_HASH.
  4. Now you can send it either with GET via appending query string to the URL or with POST using application/x-www-form-urlencoded and providing the prepared query string as raw body.

SSL Certificate Pinning

Not supported.

//sample certificate for the countly try server
// RSA Public Key encoded in Base64 inside -----BEGIN PUBLIC KEY----- and -----END PUBLIC KEY----- markers.
String[] certificates = new String[] {...};
CountlyConfig countlyConfig = new CountlyConfig(getApplicationContext(), COUNTLY_APP_KEY, COUNTLY_SERVER_URL);
countlyConfig.enablePublicKeyPinning(certificates);
Countly.sharedInstance().init(countlyConfig);
Objective-C Swift
config.pinnedCertificates = @[@"mycertificate.cer"];
Countly.pinnedCertificates("count.ly.cer");
Config config = new Config(COUNTLY_SERVER_URL, COUNTLY_APP_KEY, sdkStorageRootDirectory)
  .addPublicKeyPin(
    "oIssIjsNsgkqhkiG9s0BAQEFAAsCAs8AMIIBCsgKCAQEtmTk89AsQboS+sMbVoOJ\n"
    + "cXsOz0TWJqNs9M00atZasr//WcNTQvmGps66hWqhYglPFH76CWlsq34aOsHXoHyS\n"
    + "sQIDsQAs\n")
  .addCertificatePin("MIIE6zCCA9OgAwIBAgISBOnD4DLsF/BN6DfX48OHnxJeMA0GCSqGSIb3DQEBCwUA\n"
    + "ihXZ0bqI3D3luTIsKb4ld3Fwzs9E1iajevTNNGrWWqq//1nDU0L5hqbOuoVqoWIJ\n"
    + "AQIBMIIBBAYKKwYBBAHWeQIEAgSB9QSB8gDwAHYAouK/1h7eLy8HoNZObTen3GVD\n"
    + "jYuULqgPlvynDMFMG+mB\n");
            
Countly.instance().init(config);

Since the setup process for pinning differs between platforms and frameworks, it is recommended to consult platform specific examples.

Was this page helpful?
Reach out to us for any other questions.
Helpful?

Looking for more Help?