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
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.
Field | Validation Type | Required | Description |
---|---|---|---|
device | short string | Yes | examples: "iPhone6.1" , "GT-I9000" . If not found then "unknown" . |
v | integer | Yes | Reflects the version of events coming in to the collectors. Current version is 2. |
user_id | string | Yes | Use the unique device id if possible. For Android it’s the AID. Should always be the same across game launches. |
client_ts | integer | Yes | Timestamp 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_version | sdk version | Yes | The SDK is submitting events to the servers. For custom solutions ALWAYS use "rest api v2" . |
os_version | os version | Yes | Operating system version. Like "android 4.4.4" , "ios 8.1" . |
manufacturer | short string | Yes | Manufacturer of the hardware the game is played on. Like "apple" , "samsung" , "lenovo" . |
platform | platform | Yes | The platform the game is running. Platform is often a subset of os_version like "android" , "windows" etc. |
session_id | session id | Yes | Generate a random lower-case string matching the UUID format. Example: de305d54-75b4-431b-adb2-eb6b9e546014 |
session_num | unsigned integer | Yes | The 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_tracking | boolean | No | Send true if detected. Very important to always check this when using iOS idfa. |
android_id | string | No | Send this if on Android and the google_aid is not available (e.g. Android phones without the play store) |
custom_01 | short string | No | Send Custom dimension 1 if that is currently active/set. |
custom_02 | short string | No | Send Custom dimension 2 if that is currently active/set. |
custom_03 | short string | No | Send Custom dimension 3 if that is currently active/set. |
build | short string | No | Send if needed. A build version. Should be set before any events are sent. |
engine_version | engine version | No | Send if using engine. examples: “unreal 4.7” or “unity 5.6.10” |
connection_type | string | No | Send 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_idfv | string | No | Send if iOS. Apple’s identifier for vendors. This is unique per app/game. |
ios_idfa | string | No | Send if iOS. Apple’s identifier for advertisers. This is the same across apps/games. |
google_aid | string | No | Send 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.
Field | Required | Description/Validation |
---|---|---|
category | Yes | user |
+ 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.
Field | Required | Description/Validation |
---|---|---|
category | Yes | session_end |
length | Yes | Session 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.
Field | Required | Description/Validation |
---|---|---|
category | Yes | business |
event_id | Yes | A 2 part event id. [itemType]:[itemId] Read about unique value limitations here. |
amount | Yes | The amount of the purchase in cents (integer) |
currency | Yes | Currency 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_num | Yes | Similar 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_type | No | A string representing the cart (the location) from which the purchase was made. Could be menu_shop or end_of_level_shop. |
receipt_info | No | A 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
}
}
}
}
}
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.
Field | Required | Description/Validation |
---|---|---|
category | Yes | resource |
event_id | Yes | A 4 part event id string. [flowType]:[virtualCurrency]:[itemType]:[itemId]. Read about unique value limitations here. |
amount | Yes | The 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.
Examples | flowType | virtualCurrency | itemType | itemId |
---|---|---|---|---|
Life used to play level | Sink | life | continuity | startLevel |
Star used to continue level | Sink | star | continuity | resumeLevel |
Gold spent to buy rainbow boost | Sink | gold | boost | rainbowBoost |
Earned a life by watching a video ad | Source | life | rewardedVideo | gainLifeAdColony |
Bought gold with real money* | Source | gold | purchase | goldPack100 |
* When buying virtual currency for real money a business event should also be sent.
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$"
}
}
}
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
Field | Required | Description/Validation |
---|---|---|
category | Yes | progression |
event_id | Yes | A 2-4 part event id. [progressionStatus]:[progression1]:[progression2]:[progression3] |
attempt_num | No | The 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. |
score | No | An optional player score for attempt. Only sent when Status is “Fail” or “Complete”. |
+ add the default annotation fields
Examples
event_id | attempt_num | score |
---|---|---|
Start:PirateIsland:SandyHills | ||
Fail:PirateIsland:Sandyhills | 1 | 1234 |
Start:PirateIsland:Sandyhills | ||
Complete:PirateIsland:Sandyhills | 2 | 1234 |
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$"
}
}
}
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.
Field | Required | Description/Validation |
---|---|---|
category | Yes | design |
event_id | Yes | A 1-5 part event id. [part1]:[part2]:[part3]:[part4]:[part5] |
value | No | Optional 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.
Metric | Selection 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:AlienSmurf | As 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.
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.
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$"
}
}
}
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
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.
Field | Required | Description/Validation |
---|---|---|
category | Yes | error |
severity | Yes | The type of error severity. |
message | Yes | Stack 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.
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$"
}
}
}