Skip to main content

Event Types

Events are JSON objects with a certain set of required fields as well as optional fields. The exact requirements of a valid event depends on its category. Here are the available categories (event types).

  • user (session start)
  • session_end
  • business
  • progression
  • resource
  • design
  • error
  • ad events
  • impression events
info

For more information on our Event Types check out this article.

All the events share (inherit) a list of fields that we call the default annotations.

Default annotations (shared)

Each event has unique fields defining the event. But all events need to include shared fields called default annotations.

The default annotation fields define information like os_version, platform etc. and they need to be included in each event object. Some are required and some are optional.

It is recommended to create a method collecting the shared default annotations and returning a dictionary. When an event is triggered this method should be called and the specific event fields (for the event type) should be added to the dictionary. The merged dictionary is then used to create the JSON event object.

FieldValidation TypeRequiredDescription
deviceshort stringYesexamples: "iPhone6.1", "GT-I9000". If not found then "unknown".
vintegerYesReflects the version of events coming in to the collectors. Current version is 2.
user_idstringYesUse the unique device id if possible. For Android it’s the AID. Should always be the same across game launches.
client_tsintegerYesTimestamp when the event was created (put in queue/database) on the client. This timestamp should be a corrected one using an offset of time from server_time. The SDK will get the server TS on the init call (each session) and then calculate a difference (within some limit) from the local time and store this offset. When each event is created it should calculate/adjust the client_ts using the offset.
sdk_versionsdk versionYesThe SDK is submitting events to the servers. For custom solutions ALWAYS use "rest api v2".
os_versionos versionYesOperating system version. Like "android 4.4.4", "ios 8.1".
manufacturershort stringYesManufacturer of the hardware the game is played on. Like "apple", "samsung", "lenovo".
platformplatformYesThe platform the game is running. Platform is often a subset of os_version like "android", "windows" etc.
session_idsession idYesGenerate a random lower-case string matching the UUID format. Example: de305d54-75b4-431b-adb2-eb6b9e546014
session_numunsigned integerYesThe SDK should count the number of sessions played since it was installed (storing locally and incrementing). The amount should include the session that is about to start.
limit_ad_trackingbooleanNoSend true if detected. Very important to always check this when using iOS idfa.
android_idstringNoSend this if on Android and the google_aid is not available (e.g. Android phones without the play store)
custom_01short stringNoSend Custom dimension 1 if that is currently active/set.
custom_02short stringNoSend Custom dimension 2 if that is currently active/set.
custom_03short stringNoSend Custom dimension 3 if that is currently active/set.
buildshort stringNoSend if needed. A build version. Should be set before any events are sent.
engine_versionengine versionNoSend if using engine. examples: “unreal 4.7” or “unity 5.6.10”
connection_typestringNoSend if found. This will give the connection status of a device – how the device is connected to the internet (or if not). (offline, wwan, wifi, lan)
ios_idfvstringNoSend if iOS. Apple’s identifier for vendors. This is unique per app/game.
ios_idfastringNoSend if iOS. Apple’s identifier for advertisers. This is the same across apps/games. Send this always (on iOS) and make sure to ALWAYS check if user has enabled “limited_ad_tracking”. If so then add the field mentioned elsewhere and targeting for that idfa will not happen.
google_aidstringNoSend if Android. Google’s identifier for advertisers. This is the same across apps/games.

session_num

Do something like the following to track this value.

  • Use a persistent DB like SQLite for storing queued events and other information.
  • Create a table called key_value with key & value columns. Use this column for storing all variables cross game-launch.
  • For counting session number use a key called session_num.
  • When a session is started this row should be retrieved. Increment the value and update row.

JSON Validation Schema

The collector servers will validate these fields and reject any event not passing.

{
"description": "Schema for shared event attributes",
"id": "shared",
"type": "object",
"properties": {
"v": {
"type": "integer",
"required": true,
"minimum": 2,
"maximum": 2
},
"user_id": {
"type": "string",
"required": true
},
"ios_idfa": {
"type": "string",
"required": false
},
"ios_idfv": {
"type": "string",
"required": false
},
"google_aid": {
"type": "string",
"required": false
},
"android_id": {
"type": "string",
"required": false
},
"googleplus_id": {
"type": "string",
"required": false
},
"facebook_id": {
"type": "string",
"required": false
},
"limit_ad_tracking": {
"type": "boolean",
"enum": [true],
"required": false
},
"logon_gamecenter": {
"type": "boolean",
"enum": [true],
"required": false
},
"logon_googleplay": {
"type": "boolean",
"enum": [true],
"required": false
},

"custom_01": {
"type": "string",
"maxLength": 32,
"required": false
},
"custom_02": {
"type": "string",
"maxLength": 32,
"required": false
},
"custom_03": {
"type": "string",
"maxLength": 32,
"required": false
},
"client_ts": {
"type": ["integer", "null"],
"pattern": "^([0-9]{10,11})$",
"required": false
},
"sdk_version": {
"type": "string",
"required": true,
"pattern": "^(((ios|android|unity|unreal|corona|marmalade|xamarin|gamemaker|flash|cocos2d|javascript|tvos|uwp|wsa|buildbox|defold|cpp|mono|lumberyard|stingray|frvr|air|uwp_cpp|tizen|construct|godot|stencyl|fusion|nativescript|cordova|roblox|flutter|android_meta_vr|unreal_uefn|minecraft_map|vr_chat) [0-9]{0,5}(\\.[0-9]{0,5}){0,2})|rest api v2)$"
},
"engine_version": {
"type": "string",
"required": false,
"pattern": "^(unity|unreal|corona|marmalade|xamarin|xamarin.ios|xamarin.android|xamarin.mac|gamemaker|flash|cocos2d|monogame|stingray|cryengine|buildbox|defold|lumberyard|frvr|construct|godot|stencyl|fusion|nativescript|cordova|roblox|unreal_uefn|minecraft_map)) [0-9]{0,5}(\\.[0-9]{0,5}){0,2}$"
},
"os_version": {
"type": "string",
"pattern": "^(ios|android|windows|windows_phone|blackberry|roku|tizen|nacl|mac_osx|tvos|webplayer|ps4|xboxone|uwp_mobile|uwp_desktop|uwp_console|uwp_iot|uwp_surfacehub|webgl|xbox360|ps3|psm|vita|wiiu|samsung_tv|linux|watch_os|uwp_holographic|switch|ipados|chrome|kai_os|android_meta_vr|ps5|ps6|xbox_s_x|switch_lite) [0-9]{0,5}(\\.[0-9]{0,5}){0,2}$",
"required": true
},
"manufacturer": {
"type": "string",
"maxLength": 64,
"required": true
},
"device": {
"type": "string",
"maxLength": 64,
"required": true
},
"platform": {
"type": "enum",
"required": true,
"enum": [
"ios",
"android",
"windows",
"windows_phone",
"blackberry",
"roku",
"tizen",
"nacl",
"mac_osx",
"tvos",
"webplayer",
"ps4",
"xboxone",
"uwp_mobile",
"uwp_desktop",
"uwp_console",
"uwp_iot",
"uwp_surfacehub",
"webgl",
"xbox360",
"ps3",
"psm",
"vita",
"wiiu",
"samsung_tv",
"linux",
"watch_os",
"uwp_holographic",
"switch",
"ipados",
"chrome",
"kai_os",
"android_meta_vr",
"ps4",
"ps5",
"ps6",
"xbox_s_x",
"switch_lite",
"server"
]
},
"session_id": {
"type": "string",
"pattern": "^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$",
"required": true
},
"build": {
"type": "string",
"maxLength": 32,
"required": false
},
"session_num": {
"type": "integer",
"minimum": 1,
"required": true
},
"connection_type": {
"type": "string",
"enum": ["offline", "wwan", "wifi", "lan"],
"required": false
},
"jailbroken": {
"type": "boolean",
"enum": [true],
"required": false
}
}
}

User (session start)

As session is the concept of a user spending a period of time focused on a game.

The user event acts like a session start. It should always be the first event in the first batch sent to the collectors and added each time a session starts.

FieldRequiredDescription/Validation
categoryYesuser

+ add the default annotation fields

JSON Validation Schema

The collector servers will validate these fields and reject any event not passing.

{
"description": "Schema for user event",
"id": "user",
"type": "object",
"extends": "shared",
"properties": {
"category": {
"type": "string",
"required": true,
"pattern": "^user$"
}
}
}

Session end

Whenever a session is determined to be over the code should always attempt to add a session end event and submit all pending events immediately.

Only one session end event per session should be activated.

FieldRequiredDescription/Validation
categoryYessession_end
lengthYesSession length in seconds

+ add the default annotation fields

On mobile devices the session normally stops when a user taps the home button and the app is not visible.

On certain phones (like Android) there is also a pause and stop event where the session will end on stop but also if pause has been active for 20 seconds or so.

Different platforms implement varying behaviour but all with the same goal – to end the session when the user is no longer focused on the game.

Session length

Session length is the amount of seconds spent focused on a game. Whenever a session starts the current timestamp should be stored in a variable. When session end is triggered this values is used to calculate the session length.

Detecting missing session end on game launch

Sometimes the session end event could not be added as the game closed without giving time to finish processing. It is recommended to implement code that is able to detect this on game launch and add the missing session end with correct session length.

This should be solved using a local storage that will work cross session/game-launch. In our official SDK implementation we use SQLite. The following practise describe how to detect and submit a missing session end.

  • Use a persistent DB like SQLite for storing queued events and other information.
  • Create a table called session_end with the columns session_start_ts, session_id & last_event_default_annotations.
  • Each time (in that session) an event is added to the queue (excluding session end) this row is updated on the column last_event_default_annotations containing the shared default annotations for the particular event (including the field client_ts for the event). If the event added is a session end, then simply delete the row matching the session.
  • On game launch query the session_end table. If an entry exists then there is a session that did not have time to add a session end. Look inside the last_event_default_annotations and find client_ts. Use that value with the session_start_ts to calculate session length. Use the last_event_default_annotations to create the session end event. Add event and submit.

JSON Validation Schema

The collector servers will validate these fields and reject any event not passing.

{
"description": "Schema for session end event",
"id": "session_end",
"type": "object",
"extends": "shared",
"properties": {
"length": {
"type": "integer",
"minimum": 0,
"maximum": 172800,
"required": true
},
"category": {
"type": "string",
"required": true,
"pattern": "^session_end$"
}
}
}

Business Events

Business events are for real-money purchases.

FieldRequiredDescription/Validation
categoryYesbusiness
event_idYesA 2 part event id. [itemType]:[itemId] Read about unique value limitations here.
amountYesThe amount of the purchase in cents (integer)
currencyYesCurrency needs to be a 3 letter upper case string to pass validation. In addition the currency needs to be a valid currency for correct rate/conversion calculation at a later stage. Click here for a list valid currency values. json.
transaction_numYesSimilar to the session_num. Store this value locally and increment each time a business event is submitted during the lifetime (installation) of the game/app.
cart_typeNoA string representing the cart (the location) from which the purchase was made. Could be menu_shop or end_of_level_shop.
receipt_infoNoA JSON object that can contain 3 fields: store, receipt and signature. Used for payment validation of receipts. Currently purchase validation is only supported for iOS and Android stores. For iOS the store is apple and the receipt is base64 encoded. For Android the store is google_play and the receipt is base64 encoded + the IAP signature is also required.Look at the validation schema later to see the structure of this receipt_info object.

+ add the default annotation fields

itemType & itemId

The itemType is like a category/folder for items and the ItemId is an identifier for what has been purchased. They are separated by a semicolon.

examples

BlueGemPack:GameAnalytics.blue_gems_50

BlueGemPack:GameAnalytics.blue_gems_100

BlueGemPack:GameAnalytics.blue_gems_500

Boost:MegaBoost

Boost:SmallBoost

In the GameAnalytics tool it is possible to select the itemId and get detailed information. But it is also possible to select the itemType and thereby get aggregated values for all itemIds within. This could be visualised like a histogram for each or simply showing the total revenue for all BlueGemPacks over time.

Transaction number

Do something like the following to track this value:

  • Use a persistent DB like SQLite for storing queued events and other information.
  • Create a table called key_value with key value columns. Use this column for storing all variables cross game-launch.
  • For the counting transactions use a key called transaction_num.
  • Each time a business event is triggered this row is retrieved, value is incremented and row is updated.

Purchase validation

The result of validating receipts is monetization metrics being divided into valid and non-valid in the GameAnalytics tool.

Currently purchase validation is only supported for iOS and Android stores. We are working on adding more ways (stores) for validating purchases. This feature is not meant to provide validation inside the game to block hackers. It is intended to provide valid numbers in GameAnalytics by flagging business events from monetizer hacks. Simply exclude the receipt_info field when using stores that are not supported yet.

JSON Validation Schema

The collector servers will validate these fields and reject any event not passing.

{
"description": "Schema for business event",
"id": "business",
"type": "object",
"extends": "shared",
"properties": {
"amount": {
"type": "integer",
"required": true
},
"currency": {
"type": "string",
"pattern": "^[A-Z]{3}$",
"required": true
},
"event_id": {
"type": "string",
"pattern": "^[A-Za-z0-9\\s\\-_\\.\\(\\)\\!\\?]{1,64}:[A-Za-z0-9\\s\\-_\\.\\(\\)\\!\\?]{1,64}$",
"required": true
},
"cart_type": {
"type": "string",
"maxLength": 32,
"required": false
},
"transaction_num": {
"type": "integer",
"minimum": 0,
"required": true
},
"category": {
"type": "string",
"required": true,
"pattern": "^business$"
},
"receipt_info": {
"type": "object",
"required": false,
"properties": {
"receipt": {
"type": "string",
"required": true
},
"store": {
"type": "string",
"required": true,
"pattern": "^apple|google_play|unknown$"
},
"signature": {
"type": "string",
"required": false
}
}
}
}
}
tip

For more information on Business Events go here.

Resource Events

Resource events are for tracking the flow of virtual currency registering the amounts users are spending (sink) and receiving (source) for a specified virtual currency.

FieldRequiredDescription/Validation
categoryYesresource
event_idYesA 4 part event id string. [flowType]:[virtualCurrency]:[itemType]:[itemId]. Read about unique value limitations here.
amountYesThe amount of the in game currency (float). This value should be negative if flowType is Sink. For instance, if the players pays 100 gold for a level, the corresponding resource event will have the amount -100 added in this field.

+ add the default annotation fields

flowType

Flow type is an enum with only 2 possible string values.

  • Sink means spending virtual currency on something.
  • Source means receiving virtual currency from some action.

virtualCurrency

A custom string defining the type of resource (currency) used in the event. Some examples of possible values.

  • Boost
  • Coins
  • Gems
  • Lives
  • Stars

itemType & itemId

The itemType functions like a category for the ItemId values. The purpose/meaning of these values are different when using Sink or Source.

  • Sink item values should represent what the virtual currency was spent on.
  • Source item values should represent in what way the virtual currency was earned.
ExamplesflowTypevirtualCurrencyitemTypeitemId
Life used to play levelSinklifecontinuitystartLevel
Star used to continue levelSinkstarcontinuityresumeLevel
Gold spent to buy rainbow boostSinkgoldboostrainbowBoost
Earned a life by watching a video adSourceliferewardedVideogainLifeAdColony
Bought gold with real money*SourcegoldpurchasegoldPack100

* When buying virtual currency for real money a business event should also be sent.

caution

The resource event should not be spammed. Imagine an infinite runner game where coins are picked up quickly. Instead of submitting an event on each coin pickup the code should collect the gathered coins until the level is over and submit the amount.

JSON Validation Schema

The collector servers will validate these fields and reject any event not passing

{
"description": "Schema for resource event",
"id": "resource",
"type": "object",
"extends": "shared",
"properties": {
"event*id": {
"type": "string",
"pattern": "^(Sink|Source):[A-Za-z]{1,64}:[A-Za-z0-9\\s\\-*\\.\\(\\)\\!\\?]{1,64}:[A-Za-z0-9\\s\\-_\\.\\(\\)\\!\\?]{1,64}$",
"required": true
},
"amount": {
"type": "number",
"required": true
},
"category": {
"type": "string",
"required": true,
"pattern": "^resource$"
}
}
}
tip

For more information on Resource Events go here.

Progression Events

Progression events are used to track attempts at completing levels in order to progress in a game. There are 3 types of progression events.

  • Start
  • Fail
  • Complete
FieldRequiredDescription/Validation
categoryYesprogression
event_idYesA 2-4 part event id. [progressionStatus]:[progression1]:[progression2]:[progression3]
attempt_numNoThe number of attempts for this level. Add only when Status is “Complete” or “Fail”. Increment each time a progression attempt failed for this specific level.
scoreNoAn optional player score for attempt. Only sent when Status is “Fail” or “Complete”.

+ add the default annotation fields

Examples

event_idattempt_numscore
Start:PirateIsland:SandyHills
Fail:PirateIsland:Sandyhills11234
Start:PirateIsland:Sandyhills
Complete:PirateIsland:Sandyhills21234

progression1:progression2:progression3

The progression evnetId will end up in the tool as a selectable metric with drilldown into a hierarchy. For example…

  • Progression > Fail > PirateIsland > Sandyhills
    View Fails on specific level SandyHills on PirateIsland.
  • Progression > Complete > PirateIsland > (all).
    View Completes for all levels on PirateIsland.

It is possible to use 1, 2 or 3 values depending on your game. For example…

  • PirateWorld:PirateIsland:SandyHills
  • PirateIsland:SandyHills
  • SandyHills

Start

The Start progression event should be called when a user is starting an attempt at completing a specific level. The attempt will stop once Fail or Complete is called.

When a Start event is called the progression event_id (excluding the progression status) should be stored locally. For example Start:PirateIsland:SandyHills should store PirateIsland:SandyHills locally.

If the Start event is called when there is an ongoing attempt already in progress the code should add a Fail event for that attempt, before adding the new Start event.

Fail

The Fail progression event should be called when a user did not complete an ongoing level attempt. Add a score value if needed.

The user ran out of time or did not get enough points. Score screen is often shown.

The user is exiting the game.

A Start event was called during an ongoing attempt.

Complete

The Complete progression event should be called when a user did complete a level attempt. Add a score value if needed.

Handling attempt_num

The attempt_num is the number of times the user performed an attempt at completing a specific level (tracked for each progression event id). Once a complete is registered the counting is reset. Do something like the following to track the incrementing of progression attempts.

Use a persistent DB like SQLite for storing queued events and other information.

  • Create a table called progression with progression_event_id attempt_num columns.
  • On progression Fail look inside the table for the specified progression_event_id
    • if it’s there then increment attempt_num for the row by 1 and use this value in the event
    • if it’s not there then create row for progression_event_id with attempt_num equal to 1 and use 1 in the event
  • On progression Complete look inside the table for the specified progression_event_id
    • if it’s there then get the attempt_num value, use this value+1 in the event and delete the row
    • if it’s not there then use 1 as the attempt_num

JSON Validation Schema

The collector servers will validate these fields and reject any event not passing.

{
"description": "Schema for progression event",
"id": "progression",
"type": "object",
"extends": "shared",
"properties": {
"event*id": {
"type": "string",
"pattern": "^(Start|Fail|Complete):[A-Za-z0-9\\s\\-*\\.\\(\\)\\!\\?]{1,64}(:[A-Za-z0-9\\s\\-_\\.\\(\\)\\!\\?]{1,64}){0,2}$",
"required": true
},
"attempt_num": {
"type": "integer",
"minimum": 0,
"required": false
},
"score": {
"type": "integer",
"required": false
},
"category": {
"type": "string",
"required": true,
"pattern": "^progression$"
}
}
}
tip

For more information on Progression Events go here.

Design Events

Every game is unique! Therefore it varies what information is needed to track for each game. Some needed events might not be covered by our other event types and the design event is available for creating a custom metric using an event id hierarchy.

FieldRequiredDescription/Validation
categoryYesdesign
event_idYesA 1-5 part event id. [part1]:[part2]:[part3]:[part4]:[part5]
valueNoOptional value. float.

+ add the default annotation fields

Examples

GamePlay:Kill:[monster_type] and value equal to points gained. In the GameAnalytics explore tool you can now select the following metrics.

MetricSelection Description
GamePlay (all)Show all count/sum etc. from all events beneath GamePlay. These metrics are not that useful, but GamePlay is defined as a group to contain all gameplay related metrics. Other root groups could be Ads, Performance, UI etc.
GamePlay:Kill (all)Show count/sum etc. for all things tracked under GamePlay:Kill. Show either aggregated count/sum over time or histogram with bars for each monster_type. All points earned by killing (over time), the average points earned by killing (over time) or number of times something was killed (count).
GamePlay:Kill:AlienSmurfAs the above, but only showing the specific monster_type.
GamePlay:Kill:(AlienSmurf + HumanRaider)Almost same as above. Show both AlienSmurf and HumanRaider values in chart and values.

This example was a simple part of what the Explore tool can provide. Design events are used for many other things like Funnels, Segments, Cohorts etc.

info

The design events grant more freedom for deep hierarchies and therefore these have a limitation; custom dimension and progression filters will not work on design events. Therefore you cannot (at the moment) filter a selected design event metric by custom or progression dimensions in the tool.

caution

The design event id structure (with the colon separation) represents a tree hierarchy. It is very important to not generate an excessive amount of unique nodes possible in the event hierarchy tree.

A bad implementation example. kill:[monster_type]:[armor_used]:[weapon_used]:[damage_done]

Potential unique values for each. monster_type=100 armor_used=300 weapon_used=300 damage_done=5000

100 * 300 * 300 * 5000 = 45.000.000.000 possible nodes.

This is far too many. The damage should be put as a value and not in the event string. Even though we remove the damage string it will still be 90M nodes. The processing will be blocked when doing this and could cause other problems when browsing our tool.

Only use deep hierarchies when using values with a small distinct pool of possible values. Do NOT use any dynamic values inside the string like timestamps or other data that change over time.

The maximum amount of unique nodes generated should be around 10k.

The collector servers will validate these fields and reject any event not passing.

{
"description": "Schema for design event",
"id": "design",
"type": "object",
"extends": "shared",
"properties": {
"event*id": {
"type": "string",
"pattern": "^[A-Za-z0-9\\s\\-*\\.\\(\\)\\!\\?]{1,64}(:[A-Za-z0-9\\s\\-_\\.\\(\\)\\!\\?]{1,64}){0,4}$",
"required": true
},
"value": {
"type": "number",
"required": false
},
"category": {
"type": "string",
"required": true,
"pattern": "^design$"
}
}
}
tip

For more information on Design Events go here.

Error Events

An Error event should be sent whenever something horrible has happened in your code – some Exception/state that is not intended.

The following error types are supported:

  • debug
  • info
  • warning
  • error
  • critical
caution

Do not send more than 10 error events per game launch!

The error events can put a high load on the device the game is running on if every Exception is submitted. This is due to Exceptions having a lot of data and that they can be fired very frequently (1000/second is possible).

The GameAnalytics servers will block games that send excessive amounts of Error events.

Simple solution

Keep track of how many is sent and stop sending when the threshold is reached.

More advanced

Keep track of each type of Exception sent in a list. If an error event matches a type already sent then ignore. If 10 types have been sent then stop sending. This will ensure that 10 similar Error events fired quickly will not result in other types not being discovered.

The idea is that developers should discover an error in the GameAnalytics tool and then fix the cause by submitting a new version of the app. Even with the limit of 10 error events this should still be possible.

FieldRequiredDescription/Validation
categoryYeserror
severityYesThe type of error severity.
messageYesStack trace or other information detailing the error. Can be an empty string.

+ add the default annotation fields

The error event supports large stack traces and therefore these events have a limitation; custom dimension and progression filters will not work on error events. Therefore you cannot (at the moment) filter a selected error event metric by custom or progression dimensions in the tool.

JSON Validation Schema

The collector servers will validate these fields and reject any event not passing.

tip

For more information on Error events go here.

{
"description": "Schema for error event",
"id": "error",
"type": "object",
"extends": "shared",
"properties": {
"severity": {
"type": "enum",
"enum": ["debug", "info", "warning", "error", "critical"],
"required": true
},
"message": {
"type": "string",
"maxLength": 8192,
"required": true
},
"category": {
"type": "string",
"required": true,
"pattern": "^error$"
}
}
}