Skip to main content

Setup

info

A Collector is a GameAnalytics server (one of many) that receive and collect game events being submitted using the HTTPS protocol. All official GameAnalytics SDK’s are using this same API.

Game Keys

The following keys are needed when submitting events.

  • game key
  • secret key
caution

The game key is the unique identifier for the game.

The secret key is used to protect the events from being altered as they travel to our servers.

To obtain these keys it is needed to register/create your game at our GameAnalytics tool. Locate them in the game settings after you create the game in your account.

API Endpoints

  • Only HTTPS is supported.
  • Data is submitted and received as JSON strings.
  • Gzip is supported and strongly recommended.

Production

API endpoint for production: api.gameanalytics.com

Sandbox

API endpoint for sandbox: sandbox-api.gameanalytics.com

  • Use the sandbox-api endpoint and sandbox keys during the implementation phase.
  • Switch to the production endpoint and production keys once the integration is completed.
  • Create your production keys in our GameAnalytics tool.
caution

Do NOT use production keys on the sandbox-api; it will only authorise the sandbox game keys.

Sandbox Keys

KeyCode
Game Key5c6bcb5402204249437fb5a7a80a4959
Secret Key16813a12f718bc5c620f56944e1abc3ea13ccbac

Event Processing

Events should be ingested and visible using the Live Events view inside the Realtime page within a few seconds. After a few minutes, events should also be visible within the Realtime dashboard and also across the tool.

Gzip

It is highly recommended to gzip the data when submitting.

  • Set the header Content-Encoding header to gzip
  • Gzip the events JSON string and add the data to the POST payload
  • Calculate the HMAC Authorization header using the gzipped data
info

The collector has a POST size limit of 1MB.

Look at the code example for gzip and HMAC in python below. We also provide a full example with python at the end of this document.

import gzip
from StringIO import StringIO
import base64
import hmac
import hashlib

events_JSON_test = '["test":"test"]'
game_secret = '16813a12f718bc5c620f56944e1abc3ea13ccbac'

def get_gzip_string(string_for_gzip):
zip_text_file = StringIO()
zipper = gzip.GzipFile(mode='wb', fileobj=zip_text_file)
zipper.write(string_for_gzip)
zipper.close()
enc_text = zip_text_file.getvalue()
return enc_text

def hmac_auth_hash(body_string, secret_key):
return base64.b64encode(hmac.new(secret_key, body_string, digestmod=hashlib.sha256).digest())

# the gzipped payload
gzipped_events = get_gzip_string(events_JSON_test)

# the HMAC hash for the Authorization header
HMAC_from_gzip_contents = hmac_auth_hash(gzipped_events, game_secret)

Authentication

Authentication is handled by specifying the Authorization header for the request.

The authentication value is a HMAC SHA-256 digest of the raw body content from the request using the secret key (private key) as the hashing key and then encoding it using base64.

Look at the code examples for shell, python and C#.

echo -n '<body_contents>' | openssl dgst -binary -sha256 -hmac "<game secret>" | base64

Headers

HeaderValueComment
AuthorizationHMAC HASHThe authentication hash.
Content Typeapplication/jsonRequired.
Content EncodinggzipOptional. Set only if payload is gzipped.
Content Length[ length of payload ]Optional. Set if possible.

Routes

2 routes are supported.

  • Init: POST /v2/<game_key>/init
  • Events: POST /v2/<game_key>/events
info

The API version is part of the URL. Currently it’s v2. Replace the <game_key> in the URL with your specific game key.

Validation for JSON body content is described later in the documentation.

Init / Remote configs

The init call should be requested whenever a new session starts or at least when the game is launched.

Use the init call to retrive remote configs, A/B testing id, A/B testing variant id and to adjust client timestamp.

First run (no configs hash)

POST /remote_configs/v1/init?game_key=<game_key>&interval_seconds=<int>

On first run when you haven’t have received a configs hash yet send the init request without the configs_hash parameter.

After first run (using configs hash)

POST /remote_configs/v1/init?game_key=<game_key>&interval_seconds=<int>&configs_hash=<configs_hash>

The response from the first init request will contain a configs_hash value which you need to save for the next time you send the init request.

POST /v2/<game_key>/init HTTP/1.1
Host: sandbox-api.gameanalytics.com
Authorization: <authorization_hash>
Content-Type: application/json

{"platform":"ios","os_version":"ios 8.1","sdk_version":"rest api v2"}
HTTP/1.1 200 OK
Connection: Keep-Alive
Content-Type: application/json
Access-Control-Allow-Origin: *
X-GA-Service: collect
Access-Control-Allow-Headers: Authorization, X-Requested-With

{"enabled":true,"server_ts":1431002142,"flags":[]}

The POST request should contain a valid JSON object as in the body containing the following fields.

Query string

ParameterDescription
game_keyString. The game key expressed in lower case hexadecimal alphabet
interval_secondsInt. The duration of the requested effective range interval in seconds as a non-negative integer. An absent value will be interpreted as 0. If the interval is 0 then a configuration for the timestamp_start instant will be returned.
configs_hashString. optional configs_hash value that might have been returned in a previous request to Remote Config service.

JSON body

FieldDescription
user_idA unique identifier of the user. This is used to detect if the user should be enrolled for A/B testing
platformA string representing the platform of the SDK, e.g. “ios”
os_versionA string representing the OS version, e.g. “ios 8.1”
sdk_versionCustom solutions should ALWAYS use the string “rest api v2”
buildOptional. Build version of the app.
random_saltRandom salt used for encryption.

Server responses

A “big response” with HTTP 201 Created, or “small response” with HTTP 200 OK, if the ‘configs_hash’ indicates that the SDK still holds the relevant version of the configuration.

The server response is a JSON object with the following fields.

FieldDescription
server_tsInt (epoch).
configsArray. An array of SDK configuration objects. Not used at the moment. In the future this could contain flags set by GA servers to control SDK behaviour. Make sure the code does not break if this contain values in the future. Only sent for HTTP status 201.
keyString. key. Only sent for HTTP status 201.
valueString. value. Only sent for HTTP status 201.
start_tsInt (epoch). Start timestamp for configuration. Only sent for HTTP status 201.
end_tsInt (epoch). End timestamp for configuration. Only sent for HTTP status 201.
configs_hashString. Configs hash. Save for next time calling init request. Only sent for HTTP status 201.
ab_idString. A/B testing id. Only added if part of an A/B testing experiment. Only sent for HTTP status 201.
ab_variant_idString. A/B testing variant id. Only added if part of an A/B testing experiment. Only sent for HTTP status 201.

Example responses

A user does not participate in an experiment. Only Global configs are received.


HTTP 201 Created

{
"server_ts": 1546300000,
"configs": [
{
"key": "enable_rocket_launcher",
"value": "true",
"end_ts": null
},
{
"key": "gravity",
"value": "80",
"start_ts": 1546300111,
"end_ts": null
},
{
"key": "mushroom_power",
"value": "32",
"end_ts": 1546399999
}
],
"configs_hash": "1448877966322221"
}

A user participates in an experiment. Only variant config is received (no Global configs).

HTTP 201 Created

{
"server_ts": 1546300000,
"configs": [
{
"key": "background_color",
"value": "#ff0000",
"end_ts": null
}
],
"configs_hash": "124595093888900",
"ab_id": "aaabc",
"ab_variant_id": "1"
}

A user is in the control group. No configs are received, only experiment and variant IDs.

HTTP 201 Created

{
"server_ts": 1546300000,
"configs": [],
"configs_hash": "3105646955558893",
"ab_id": "aaabc",
"ab_variant_id": "ctrl"
}

The ‘configs_hash’ hasn’t changed. SDK should follow the Configs from the last response.

HTTP 200 OK

{
"server_ts": 1546300000
}

Adjust client timestamp

The server_ts should be used if the client clock is not configured correctly.

  • Compare the value with the local client timestamp.
  • Store the offset in seconds.
  • Use this offset whenever an event is added to adjust the local timestamp (client_ts) for the event

Events

POST /v2/<game_key>/events

The events route is for submitting events.

The POST body payload is a JSON list containing 0 or more event objects.

Like this example:

[
{
"category": "user",
"[event_fields]": "[event_values]"
},
{
"category": "business",
"[event_fields]": "[event_values]"
}
]

Events always need to be in a list even if you are sending only 1 event.

An event object contain all data related to a specific event triggered. Each type of event is defined by a unique category field and each require specific fields to be defined.

If the status code 200 is returned then the request succeeded and all events were collected.

Read more about error responses (looking for the reason when validation should fail) in the troubleshooting guide.

When your game implementation is running it should cache events (local db recommended) and periodically (each 20 seconds for example) submit all stored events using the events route.

When offline the code should also queue events and submit once the connection is restored. Recommended to define a maximum size for the cache that (when exceeded) will activate a trim deleting all events for the oldest sessions.

POST /v2/<game_key>/events HTTP/1.1
Host: sandbox-api.gameanalytics.com
Authorization: <authorization_hash>
Content-Type: application/json

[
{"category": "user", "<event_fields>": ""},
{"category": "business", "<event_fields>": ""}
]
HTTP/1.1 200 OK
Connection: Keep-Alive
Content-Type: application/json
Access-Control-Allow-Origin: *
X-GA-Service: collect
Access-Control-Allow-Headers: Authorization, X-Requested-With

Server Implementation

It is possible to submit events from a server on behalf of clients. A scenario could be a multiplayer server keeping track of sessions for all clients connected.

Even though this is possible it is not recommended.

Country lookup by IP

The GameAnalytics collectors will inspect the request IP and perform a GEO look-up for country information.

Note that GameAnalytics will not store the IP on our servers. It is only used to resolve to a country abbreviation string like US.

If all the events are submitted by a single server then the country for all users would be the same (the country the server is located in). This can be solved by forwarding the client IP in the request when sending events.

This is done using the standard X-Forwarded-For HTTP header. For example if your client has the IP 1.2.3.4 then the header to be included in the request should look like this…

X-Forwarded-For: 1.2.3.4

Read more about this header on Wikipedia.

Request per user

As you can submit many events in the same request (a batch) it would be tempting to send events from multiple users at the same time. This is not recommended as you specify one IP per request in the header X-Forwarded-For and thus all the events submitted will be annotated with the country resolved by that single IP.

It is needed to submit a request per user and specify the IP. A way to obtain this on the server would be to…

  • collect intended events for users inside buckets per IP (user)
  • activate a synchronous loop (one after the other) that submit events for each bucket (user) using the IP in the forward header
  • wait a few seconds (15-20) and activate the loop again submitting events collected

This will make sure each user is resolved properly by country and our servers are not spammed with requests.

Session End

The server should keep track of the session time for each user. When it is detected that a user is no longer playing the game it is important to add (submit) a session end event for that user and session_id.

Python Example

Download a Python example here using the sandbox api and sandbox game key and secret. The code is implementing several key areas.

  • HMAC Authorization hash
  • gzipping
  • simple memory queue (use something like SqlLite instead)
  • server-time adjustment
  • default annotations function
  • init / event routes etc.

Run the example from the terminal:

python REST_v2_example.py

Example steps

  • make an init call
    • check if disabled
    • calculate client timestamp offset from server
  • start a session
  • add a user event (session start) to queue
  • add a business event + some design events to queue
  • add some design events to queue
  • add session_end event to queue
  • submit events in queue

Java Example

Download a Java example here using the sandbox api and sandbox game key and secret key. The code is implementing several core areas.

  • HMAC Authorization hash
  • simple queue (use something like SqlLite instead and create a local storage)
  • default annotations function
  • init / event routes and basic events composition

Run the example in a Java IDE i.e. Eclipse Oxygen 2

Example steps

  • make an init call
    • check if disabled
  • start a session (session_id generation)
  • add a user event (session start) to queue
  • add some design events to queue
  • add a business event
  • add some progression events (Start, Fail, Complete)
  • add some resource (Sink and Source)
  • add an error event (Info)
  • submit current existing events from queue
  • add session_end event and send