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.
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.
// 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]);Countly.init({
app_key: "APP_KEY",
url: "SERVER_URL",
require_consent: true // this will enable consent management
});
// to give consent {string|array}
Countly.add_consent(feature)
// to remove consent {string|array}
Countly.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)
// 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.requiresConsent = true // giving all consent during init config.enableAllConsents = true // giving partial consent during init config.consents = [CLYConsentSessions, CLYConsentEvents]; // giving consent after init Countly.sharedInstance().giveAllConsents() Countly.sharedInstance().giveConsent(forFeatures: [CLYConsentSessions, CLYConsentEvents]) Countly.sharedInstance().cancelConsent(forFeatures: [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.
// befor init Countly.salt = "your_salt";
Countly.init({
app_key: "APP_KEY",
url: "SERVER_URL",
salt: "your_salt"
});config.setParameterTamperingProtectionSalt("your_salt");config.secretSalt = @"your_salt";
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:
- 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.
- Append the salt to the end of the String value you have prepared. And calcuate sha256 hash of it.
- URL-encode the query string. After that append the calculated hash like this:
&checksum256=CALCULATED_HASH. - Now you can send it either with GET via appending query string to the URL or with POST using
application/x-www-form-urlencodedand 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);config.pinnedCertificates = @[@"mycertificate.cer"];
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.