Countly Documentation

Countly Resources

Here you'll find comprehensive guides to help you start working with Countly as quickly as possible.

SDK development guide

Do you want to develop a new SDK for Countly? Then this guide is for you. Before starting, bear in mind that there are a lot of SDKs already developed for Countly, so please check whether the SDK you are going to develop is not already available.

Initialization

To start making requests to server SDK needs 3 things: URL of the server where to make requests, app_key of the app for which to report and current device_id to uniquely identify this device.

Server URL

SDK needs to have an ability for user to specify URL for the server where their Countly instance is installed..

App Key

App key should be provided by SDK user. App should be created on Countly server. After app creation server will provide app key to the user. Same app key is used for the same app on different platforms

Device ID

Device ID is required to uniquely identify device or user. So if you have some unique user ID which you can retrieve, you can use that. If not, you can provide platform specific device identification (as Advertising identifier in Google Play Services on Android) or use existing implementations (as OpenUDID);

Other parameters

Of course you can have other platform specific parameters, like adding debug parameter, or providing different ways to generate device Ids, or allowing set any other variables or settings upon initialization.

But there are some cross platform parameters that you need to allow to set to SDK users:

  • country_code - (optional) ISO Country code for the user's country
  • city - (optional) Name of the user's city
  • location - (optional) comma separate lat and lng values

These parameters, if provided, should be added to all begin_session requests

More information here.

Countly Code Generator

If you want to understand how SDKs work by generating mobile or web code for custom events, user profiles, crash reporting and all other features that comes with Countly in general, we suggest that you use Countly Code Generator, which is a point and click service that builds necessary code for you.

Making Requests

Countly server is a simple HTTP based REST API server and all SDK requests should be made to /i endpoint with two required parameters: app_key and device_id.

Other optional parameters needs to be provide based on what this request should do. You can check list of all parameters that Countly Server can accept in /i endpoint Server API reference.

Since in some cases devices can be offline, etc, and requests should be queued. It is highly recommended to add timestamp to each request, when it was created.

Encoding URI components

Due to possible symbols as & and ? in encoded JSON strings, SDKs should encode uri components before adding them to the request and sending it to server.

Using GET or POST

By default preferred way is to make GET requests to Countly servers. But there can be some length limitation for GET requests based on specific platform or server settings. So the best practice is to make POST request when the data reaches over 2000 characters for single request.

So before making each request you need to check if data that you are going to send is less than 2000 characters then use GET, if more then use POST.

Parameter tampering

This is one of the preventive measures of Countly. if someone in the middle intercepts the request, it would be possible to change the data in the request and make another request with other data to the server, or simply make random requests to the server through retrieved app_key.

To prevent that SDK should provide an option so send a checksum along the request data.

Firstly it should be possible for developer to provide some random string as SALT to SDK as parameters or configuration options.

If this SALT is provided, right before making the request, SDK should take all the payload it about to send (all the data after ? in GET requests, including app_key and device_id) and make a sha1 hash of this data plus provided SALT and append it as checksum={hash}

if(salt){
  data += "&checksum=" + sha1Hash(data + salt);
}

If SALT is not provided, SDK should make ordinary requests without any checksums.

Request queue

In some cases user might be offline, thus can not make requests to the server. In other cases server may be down or on maintenance and can't accept requests. In both cases SDK should handle queuing and persistently storing requests made to Countly server and waiting for successful response from server before removing request from queue.

Note that requests should be made in historical order, so you must also preserve the order of your queue.

Simple flow on how request should behave looks like this.

  1. Initiating request - either new event reported, or session call, etc
  2. Creating payload - take all the parameters (including current timestamp) and values needed for request and generate payload which will be included in HTTP request
  3. This payload is inserted into queue (First In, First Out).
  4. All updates to queue should be persistently stored. Based on environment you can use storage for queue directly
  5. On some other thread there should be a request processor which takes first request in queue, applies checksum if needed, determines request type (GET or POST) based on length and makes HTTP request
    • if request is successfull, then it should be removed from queue and next request will be processed on next iteration
    • if request failed, request processor should have a cool down period, for a minute or so (configurable value) and then try same request again, till it is completed

There are multiple scenarios why request might fail, so to ensure that request was successfully delivered to server SDK need to make sure that:

  1. User has internet connection
  2. HTTP response code was successful (which is any 2xx code or code between 200 <= x < 300)

Additionally server replies with JSON object which has a property result with value success. There can be different scenarios, like when blocking specific device requests by server configuration, in which case "Success" may change to some other value, so do not completely rely on success value, but if there is no way to check HTTP response code, you can check if response contains JSON with result property.

{"result":"Success"}

If all conditions are met, it means request was successfully delivered to server.

Queue size limit

We need to limit queue size, so it would not overflow, and that syncing up would not take that much time in some specific server down for too long cases. This limit would be in amount of stored queries, and this limit should be available for end user to change as SDK settings.

In case of reaching this limit, SDK should remove older queries and insert new ones. The default limit can change from SDK needs, but suggested limit is 1000 queries.

Session flow

App Initialization

When app is initialized then SDK should send begin_session=1 request. This same request should also contain metrics parameters with maximum metrics described on /i page that can be collected from this SDK specific environment/language.

Session update

After that each minute the session should be extended by sending session_duration request with amount of seconds that passed from previous session request (begin_session or session_duration, which ever was last).

Ending session

When app exits SDK should send end_session=1 request including session_duration parameter with how many seconds passed from last session request (begin_session or session_duration, which ever was last).

Here are couple example request generated by different session lengths:

begin_session=1&metrics={...}
session_duration=60
session_duration=60
end_session=1&session_duration=30
begin_session=1&metrics={...}
end_sesson=1&session_duration=30

Session cooldown

In some cases it is hard to know for sure if session ended, like with web analytics, when user is leaving the page, and whether he will visit another one or not. That is why there is a small cooldown time of 15 seconds. If end_session request is sent and then begin_session request is sent within 15 seconds, it will be counted as the same session and session duration will extend this session, instead of applying to new one.

This makes it easier to call end_session on each page unload without worrying of starting new session if user visits another page.

If you don't need this behavior, simply pass ignore_cooldown=true parameter to all session requests and server will not extend session, but always count as new one.

15 seconds cool down is default value and can be configured on the server, so don't rely on it being 15 seconds.

Recording time of data

In order to properly report and process data (especially queued data), you should also provide a time when data was recorded. With each request, you need to provide 3 parameters:

  • timestamp: 13 digit UTC millisecond unique timestamp of the moment of action
  • hour: Current user local hour (0 - 23)
  • dow: Current user day of the week (0-sunday, 1 - monday, ... 6 - saturday)
  • tz: Current user time zone in minutes (120 for UTC+02, -300 for UTC-05)

As multiple events can be combined in single request, you should also provide these parameter automatically in every event object.

The suggested millisecond timestamp should be unique, meaning if if events were reported in the same timestamp, SDK should update millisecond timestamp in the order events were reported. The pseudo code to unique millisecond timestamp can look like this:

//variable to hold last used timestamp
lastMsTs = 0;

function getUniqueMsTimestamp(){
	//get current timestamp in miliseconds
	ts = getMsTimestamp();
  
  //if last used timestamp is equal or greater
  if(lastMsTs >= ts){
  	//increase last used
    lastMsTs++;
  }
  else{
  	//store current timestamp as last used
  	lastMsTs = ts;
  }
  //return timestamp
  return lastMsTs;
}

If it is not possible to use millisecond timestamp on specific platform, then you can also use 10 digit UTC seconds timestamp.

API of SDK

Depending on SDKs environment/language there could be different set of features supported. Some of them can be supported on any platforms, others are quite platform specific. For example, a desktop app type may not be providing telecom operator information.

Note that function and argument namings are just examples of what it could be. Try to follow your platform/environment/language best practices when creating and naming functions and variables.

Here is a list of things your SDK could support:

Core features

Core features are minimal set of features that SDK should support and these features are platform independent.

Initialization

In official SDKs Countly is used as singleton object or basically object with shared instance. Still there are some parameters that need to be provided before SDK could work, so usually there is a init method which accepts URL, app key and device_id (or SDK generates it itself if not provided):

Countly.init(string url="https://try.count.ly", string app_key, string device_id, ...)

Session Flows

Most of official SDKs implement automatic session handling, meaning SDK users don't need to bother with session calls separately. However, it is a good practice to provide a way to disable automatic session handling and allow SDK users to make session calls themselves through methods like

  • Countly.begin_session()
  • Countly.session_duration(int seccond)
  • Countly.end_session(int seconds)

Here is a documentation that shows how you can report sessions through our API.

Device metrics

Metrics should be only reported together with begin_session=1 parameter on every session start. Collect as much metrics as possible, or allow providing some values by user upon initialization. Possible metrics are listed in API Reference.

One thing that we should agree on is identifying platforms with the same string over all SDKs, so here is the list of how we would suggest identifying platforms for server through _os metric.

  • Android - for Android
  • BeOS - for BeOS
  • BlackBerry - for BlackBerry
  • iOS - for iOS
  • Linux - for Linux
  • Open BSD - for Open BSD
  • os/2 - for OS/2
  • macOS - for Mac OS X
  • QNX - for QNX
  • Roku - for Roku
  • SearchBot - for SearchBots
  • Sun OS - for Sun OS
  • Symbian - for Symbian
  • Tizen - for Tizen
  • tvOS - for Apple TV
  • Unix - for Unix
  • Unknown - if operating system is unknown
  • watchOS - for Apple Watch
  • Windows - for Windows
  • Windows Phone for Windows Phone

SDK Meta Data

SDK should send following meta data with every begin_session request.

  • SDK name:

Query String Key: sdk_name
Query String Value: [language]-[origin]-[platform]
Example: &sdk_name=objc-native-ios

  • SDK version:

Query String Key: sdk_version
Query String Value: SDK version as string
Example: &sdk_version=16.10

Events

Events (or custom events) are the basic Countly reporting tool that reports when something has happened in the app. Someone clicked a button, did specific action, etc, all of that could be recorded as an event.

Events should be provided by the SDK user, who knows what's important for the app to log. Also events can also be used to report some Countly internal events, starting with [CLY]_ prefix that vary per feature implementation on different platforms.

Event must contain key and count properties. If count is not provided it should default to 1.
Optionally user can also provide sum property (for example of in app purchase events), dur property for recording some duration/period of time and segmentation as map with keys and values for segmentation.

More on event format can be found in API Reference.

Here are couple of example events:

User logged in the game

  • key = login
  • count = 1

User completed second a level in the game with score 500

  • key = level_completed
  • count = 1
  • segmentation = {level=2, score=500}

User purchased something in the app worth 2.99 in main screen

  • key = purchase
  • count = 1
  • sum = 2.99
  • dur = 30
  • segmentation = {screen=main}

As you can imagine, your SDK should provide methods to cover these combinations, either by defaulting values or by function parameter overloading, etc:

  • Countly.event(string key)
  • Countly.event(string key, int count)
  • Countly.event(string key, double sum)
  • Countly.event(string key, double duration)
  • Countly.event(string key, int count, double sum)
  • Countly.event(string key, map segmentation)
  • Countly.event(string key, map segmentation, int count)
  • Countly.event(string key, map segmentation, int count, double sum)
  • Countly.event(string key, map segmentation, int count, double sum, double duration)

Note: count value defaults to 1 internally if not specified.

Timed Events

Basically you can report time with dur property in an event. It is a good practice to allow user to measure some periods internally using SDK API. For that purpose, SDK needs to provide methods below.

  • startEvent(string key) - which internally will save event key and current timestamp in the associative array/map

  • endEvent(string key, map segmentation, int count, double sum) - which will take event starting timestamp by event key from the map, get current timestamp and calculate the duration of the event and fill it up as dur property and report an event as you would report any regular event.

  • endEvent(string key) - which will simply end event with 1 as count, 0 as sum and nil as segmentation values.

If it is possible, SDK can provide a way to start multiple timed events with same key, like returning event instance in the method and then calling end method on that instance.

If not then in case already started events tried to be started again or already ended or not yet started events tried to be ended, these calls should be ignored or provide informative error.

User Details

Your SDK does not need to have a platform specific way to get user data, if it is not possible on your platform. But you need to provide a way for a developer to pass this information to SDK and send it to Countly server.

For that you can create a method to accept object with key/values about user that are described here, or provide a parameterized method to pass the information about user. Note that all fields are optional.

Additionally there could be custom key values added to user details, in this case you would need to provide a means to set them:

  • Countly.user_details(map details)
  • Countly.user_custom_details(map custom_details)

You can find more information on what data can be set for a user in this link.

Modifying custom data properties

You should also provide an option to modify custom user data, like increase value on server by 1, etc. Since there are many operations you can do with that data, it is recommended to implement a subclass for this API, which can be retrieved through Countly instance.

The standard methods SDK should provide are (provided as pseudo code and naming conventions may differ from platform to platform):

  • Countly.userData.set(string key, string value)
  • Countly.userData.setOnce(string key, string value)
  • Countly.userData.increment(string key)
  • Countly.userData.incrementBy(string key, double value)
  • Countly.userData.multiply(string key, double value)
  • Countly.userData.max(string key, double value)
  • Countly.userData.min(string key, double value)
  • Countly.userData.push(string key, string value)
  • Countly.userData.pushUnique(string key, string value)
  • Countly.userData.pull(string key, string value)
  • Countly.userData.save() //send data to server

Note: make sure push, pushUnique and pull parameters when reporting to server, can provide multiple values for same property as array.

Here is more information on how to report this data to server

Custom Device ID / Changing Device ID

There are 3 main cases when SDK user might want to provide custom device ID to identify device/user.

1. Tracking same user across multiple devices

In this case app developers need to provide their own way to identify users on initialization. So, Countly SDK needs to provide a way to set custom device ID upon initialization and store it persistently for next session use. If there is an already stored device ID, Countly SDK should use stored one primarily, unless it is forced to do otherwise.

2. Tracking multiple users on same device

In addition to initialization, developers may need to change device ID while the app is running. For example when an end-user signs out and another end-user signs in. In this case Countly SDK needs to provide a way to change device ID at any point while the app is running. It should replace internally used device ID with the new one, and use it for all new requests, and persistently store it for next sessions. So, Countly SDK should follow these steps:

  • Add currently recorded but not queued events to request queue
  • End current session
  • Clear all started timed-events
  • Change device ID and store it persistently for next session use
  • Begin new session with new device ID

3. Tracking an unauthenticated user who becomes authenticated later

Developers may need to change device ID to their own internal user ID and merge server side data previously generated by user while he/she was unauthenticated. It is similar to Case 2 but Countly SDK needs to merge data on server too. In order to make a proper transition Countly SDK should follow these steps:

  • Temporarily keep current device ID
  • Change device ID and store it persistently for next session use
  • Use old_device_id API with temporarily kept old device ID to merge data on the server
  • No need to end and restart current session, or clear started timed-events

To summarize, Countly SDK should provide a proper way to change device IDs for all 3 cases.

Note: If new and current device ID are exactly same, then Countly SDK must ignore this change call.

Additional parameters

There are also optional, additional parameters. f you can get them on your platform, then you can append them to any/every request. But if not, it might be a good idea to allow SDK user to optionally provide such values.

If values are not provided, Countly server will try to determine them automatically based on all other provided data.

  • Countly.country_code(string country_code)
  • Countly.city(string city)
  • Countly.location(double latitude, double longitude)
  • Countly.ip_address(string ip_address)

Here is more information on possible additional API parameters.

Push Notifications

Push notifications are platform specific and not all platforms have them, but if your platform has, you would need to register your device to push notification server and send the token to Countly server. For more information, please click here for API calls.

From SDK API point of view, there could be one simple function to enable push notifications for Countly server:

Countly.enable_push()

Crash reporting

On some platforms it is possible to automatically detect errors and crashes. In this case, your SDK can report them to Countly server. Just like other similar functions, this is also optional so if a crash report is not sent, it won't be displayed on dashboard under Crashes section. Here is more information on Crash reporting parameters that you can use in your SDK.

For crashes, all information except app version and OS are optional, but you should collect as much information about device as possible to make sure that each crash can be more identifiable with additional data. You should also provide a way for user to log errors manually (for example logging handled exceptions, which are not fatal).

Basically in automatically captured errors, you should set _nonfatal property to false, while on user logged errors, _nonfatal property should be true. You should also provide a way to set custom key/values to be reported as segments with crash reports either by providing global default segments, or setting separately for automatically tracked errors and user logged errors.

Additionally there should be a way for SDK user to leave breadcrumbs that would be submitted together with crash reports. In order to collect breadcrumbs as logs, create an empty array on initialization and provide a method to add breadcrumbs as strings into that array as elements for log. And upon crash, concatenate array with new line symbols and submit under _logs property. There is no need to persistently save those logs on device, since we want to have a clean log on each app start.

The end API could look like this (but it should be totally based on specific platform error handling)

  • Countly.enable_auto_error_reporting(map segments)
  • Countly.log_handled_error(string title, string stack, map segments)
  • Countly.log_unhandled_error(string title, string stack, map segments)
  • Countly.add_breadcrumb(string log)

Views

Reporting views would allow to analyze which views/screens/pages did the app user visit, and how long he spent on specific view. If it is possible to automatically determine when user visits specific view in your platform, then you should provide an option to automatically track views. Also, it is also important to provide a way to track views manually. Here is more information on view tracking API.

Let's start with manual view tracking, as it should be available on any platform. First, you need to have 2 internal private properties, as string lastView and int lastViewStartTime. Then, create internal private method reportViewDuration, which checks if lastView is null, and if not it should report duration for lastView, by calculating it from current timestamp and lastViewStartTime.

After those steps, provide a reportView method to set view name as string parameter, inside this method call reportViewDuration, to report duration of previous view if any, and then set provided view name as lastView and current timestamp as lastViewStartTime. And report view as event with visit property and segment as your platform name. Additionally if this is the first view user visits in this app session, then report start property as true too. You also need to call reportViewDuration on app exit event.

After manual view tracking is implemented, you can also implement automatic view tracking, if it is possible on your platform. In order to implement that, you need to catch your platform's specific event when view is changed can call your implemented reportView method with view name.

Additionally you need to implement enabling and disabling automatic view tracking, as well as checking status if automatic view tracking is currently enabled or not.

The pseudo code to implement view tracking could look like this:

class Countly {
    String lastView = null;
    int lastViewStartTime = 0;
    boolean autoViewTracking = false;
    
    private void reportViewDuration(){
        if(lastView != null){
             //create event with parameters and 
             //calculating dur as getCurrentTimestamp()-lastViewStartTime
        }
    }
    
    void onAppExit(){
        reportViewDuration();
    }
    
   void onViewChanged(String view){
      if(autoViewTracking)
          reportView(view);
   }
    
    public void reportView(String name){
        //report previous view duration
        reportViewDuration();
        lastView = name;
        lastViewStartTime = getCurrentTimestamp();
        //create event with parameters without duration
       // duration will be calculated on next view start or app exit
    }
    
    public void setAutoViewTracking(boolean enable){
        autoViewTracking = enable;
    } 
    
    public boolean getAutoViewTracking(){
        return autoViewTracking;
    }
}

Additionally if you platform supports actions on view, like clicks, you can report them too.
Here is more information on reporting actions for views.

Star-Rating

If possible, SDK should provide a simple 1-to-5 star-rating interface for getting user's feedback about the application. Interface will have a simple message explaining what it is for, a 1-to-5 star meter for getting users rating, and a dismiss button, incase user does not want to give a rating. This star-rating has nothing to do with App Store/Google Play Store ratings and reviews. It is just for getting a brief feedback from users, to be displayed on Countly dashboard.

After user gives a rating, a reserved event will be recored with [CLY]_star_rating as key and following as segmentation dictionary:

  • platform: on which application runs
  • app_version: application's version number
  • rating: user's 1-to-5 rating

If user dismisses star rating dialog without giving a rating event will not be recorded. Star-rating dialog's message and dismiss button title can be customized using properties on initial configuration object.

CountlyConfiguration.starRatingMessage = "Custom Message";
CountlyConfiguration.starRatingDismissButtonTitle = "Custom Dismiss Button Title";

If not set explicitly, message should be "How would you rate the app?" and dismiss button title will be "Dismiss" or corresponding localized versions depending on device language.

Star-rating dialog can be displayed in 2 ways:

1. Manually by developer

Star-rating dialog will be displayed when developers calls the specified method like askForStarRating. Optionally, there will be a callback method indicating user's 1-to-5 rating value for the developer, incase developer wants to user user's rating.

Countly.askForStarRating(callback);

There is no limit on how many times star-rating dialog can be displayed manually.

2. Automatically depending on session count

Star-rating dialog will be displayed when application's session count reaches specified limit, once for each new version of the application. So, SDK should keep track of session count for each app version locally, and compare to specified count on each app launch. This session count limit can be specified on initial configuration.

CountlyConfiguration.starRatingSessionCount = 5;

Once star-rating dialog is displayed automatically, it will not be displayed again unless there is a new app version.

There should be an optional flag called starRatingDisableAskingForEachAppVersion on initial configuration, to force star-rating dialog to be displayed only once for app lifetime, instead of for each new version.

CountlyConfiguration.starRatingDisableAskingForEachAppVersion = false;

SDK development guide