Measurement API
The TUNE Measurement API allows you to measure ads, sessions (installs and opens), or events asynchronously. This notification may either occur server-side directly from your platform or asynchronously client-side.
If you are an advertiser and would like to use the Measurement API as an SDK-less solution, please contact your account manager for details.
Setup & Essentials
Review and learn about the TUNE Server APIs. Establish core tasks, authenticate resources, create user permissions, and connect to various TUNE endpoints.
Best Practices for Event Measurement API
While the pre-defined app events in the MobileAppTracking (MAT) Measurement API should be sufficient for most cases, we recognize that there may be occasions where you need to define your own custom event(s). This document clarifies how our tracking engine handles custom events, particularly if you define a custom event that is already pre-defined by MAT, and/or there is a mismatch between your custom event API call and the event configuration you have setup in the MAT platform/app.
If the API call does not reference a pre-defined event (such as "action=purchase" or "action=login"), then it follows the custom event convention described at Measuring Other Custom Events, where custom event API calls have "conversion" as the action ("action=conversion").
It is not necessary to pre-configure events in the MAT platform. For pre-defined events, the event type is already set. For custom events, you can simply include a unique "site_event_name" value in your API call. For example:
action=conversion&site_event_name=review_submitted
MAT's tracking engine will automatically create a corresponding event type based on the "site_event_name" value.
The only time you need to define an event (and its type) in the MAT platform is when you later want to use an API call that references the specific event by event ID or event name, or when you want to change the Type of an existing custom event.
To define an event in MAT:
- In the navigation (in the left pane), click Mobile Apps.
- On the Mobile Apps page (in the right pane), click the mobile app for which you want to define an event.
- On the page for your mobile app, click Add Event as shown in the following screenshot.
- In the Add Event dialog box, from the Event Type drop-down list, select Event, provide an event name, and then click Finish as shown in the following screenshot.
- On the page for your mobile app, note the Event ID and Name for your custom event as shown in the following screenshot.
- Now in your API call, reference the event ID or name as described at Measuring Custom Events.
To change the event type for an existing custom event:
- Repeat steps 1 and 2 above.
- On the page for your mobile app, click the event for which you want to change the type as shown in the following screenshot.
- On the "event name" page, click Edit Details as shown in the following screenshot.
- On the Edit Event page, from the Event Type drop-down list, select the new desired event type, and then click Update (at the very bottom) as shown in the following screenshot.
Potential Conflicts
There are two cases where potential conflicts may arise, and both involve mismatched event types:
- You call a pre-defined event that has already been defined in the MAT platform but the event type you select in the MAT platform does not match the event type in your API call. For example, your API call is "action=purchase" but in MAT you set the event type to "Login"
- You call a custom event and define its event type in the MAT platform, but the event type does not match your API call
You do not need to manually configure pre-defined events in the MAT platform. You should only pre-configure events in the MAT platform when you need to reference a custom event by "site_event_id" or "site_event_name" in your API call.
If our tracking engine cannot match the event type of an API call to an event type defined in the MAT platform, then it looks for a matching "ref" value (derived from "site_event_name"). If a corresponding "ref" value is not found, then the tracking engine automatically defines a new event and type.
For example, you add an event in the MAT platform for the pre-defined "login" event (you select "Login" from the Event Type drop-down list and provide the name "Login". Then internally, our tracking engine creates a new event with the following properties:
- site_event_id=1
- site_event_name=Login
- event ref=login
- event type=login
So if your app makes an API call with "action=login", then our tracking engine finds an event for your app with the event type of "login" and event ref of "login".
But if you added a second "login" event through the MAT platform (for example, "Login2"), then our tracking engine creates an additional event with the following properties:
- site_event_id=2
- site_event_name=Login2
- event ref=login2
- event type=login
So if your app makes the same API call with "action=login", then our tracking engine uses the same event for your app (where event ref matches "login"). Your app would have to call "action=login2" for our tracking engine to use the second event of type "login".
Or if you only had a single event defined in the MAT platform with the following properties:
- site_event_id=1
- site_event_name=Signing In
- event ref=signingin
- event type=login
Then for any API call(s) involving "action=login", our tracking engine still uses this event because the event type = "login" and there are no other login events.
Or if the only event you defined in the MAT platform was:
- site_event_id=1
- site_event_name=Purchase
- event ref=purchase
- event type=purchase
And your API call included "action=login", then our tracking engine cannot find a "login" event type or event ref for your app, and it would create a new event of said type and use it instead.
Encrypting Parameters in a TUNE Link
While measuring ads with the Measurement API for clicks and/or impressions doesn't require authentication, the Measure Ads endpoints do allow you to include an encrypted payload of data.
This is useful if you are using client-side impression notifications or client-side click redirects and want to include cost data dynamically in the requests to measure the ads, but would prefer the actual amount not to be displayed in plain text in the URL.
Below is an example TUNE link when charging your client on a CPC basis at the rate (bid won) of $0.01 per click:
https://publisher_id.measure.mobileapptracking.com/serve?action=click
&publisher_id=19228
&site_id=2962
&ios_ifa=AAAAAA-BBBB-CCCC-11111-2222222222222
&cost_model=cpc
&cost=0.01
If you would prefer the cost model and cost parameters to not be set in plain text like in the above example, you can encrypt the data and include it in an encrypted data payload.
Below is the same example TUNE link from above, but &cost_model=cpc&cost=0.01 is encrypted and included in the the value of the data parameter:
https://publisher_id.measure.mobileapptracking.com/serve?action=click
&publisher_id=19228
&site_id=2962
&ios_ifa=AAAAAA-BBBB-CCCC-11111-2222222222222
&ckey=STRING32CHARACTERS11223344556677
&data=71b85c10d2427146dc24007c610ea647823465febf2447560d6fc02a33bd0207
Keys and Parameters
Each request in which you'd like to include an encrypted data payload, requires a Public key (Consumer Key) which is then encrypted using a Private Key.
- Private Key – used to generate the signature on both the client and server but will not be included in the request.
- Consumer Key – included with each request so that the Private Key used to create the signature can be identified.
To obtain a Private Key, please read Obtaining Your Private API Key.
To obtain a Consumer Key, please contact your TUNE Sales Engineer.
We use strings of 32 characters long for encrypting the data payload (for clicks and impressions), measuring sessions, and measuring events with the Measurement API.
The Private Key is used to create the encrypted data payload. The Consumer Key will then need to be included outside of the encrypted data payload in plain text so it can then be used to find the Private Key and encrypted data payload. Thus, you will replace the key values you want to be encrypted with the following two parameters:
- ckey – Consumer Key
- data – Encrypted data payload of parameters; generating the value for the data parameter is described in the following section.
Generating the Encrypted Data Payload
Any key value parameters you prefer not to include as plain text in the request URL can be included in the encrypted data payload.
In the PHP example below, we are hashing cost_model=cpc and cost=0.1
$consumer_key = 'STRING32CHARACTERS11223344556677';
$private_key = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx';
$params = array(
'cost' => 0.01,
'cost_model' => 'cpc'
);
$mcrypt_iv = substr($consumer_key, 0, 16);
$query_str = http_build_query($params);
$enc_raw = mcrypt_encrypt( MCRYPT_RIJNDAEL_128, $private_key, $query_str, 'cbc', $mcrypt_iv);
$enc_str = bin2hex( $enc_raw );
echo $enc_str ;
After you've generated the encrypted data payload, you'll need to include it in the URL along with the ckey parameter as seen in the example below.
https://publisher_id.measure.mobileapptracking.com/serve?action=click
&publisher_id=19228
&site_id=2962
&ios_ifa=AAAAAA-BBBB-CCCC-11111-2222222222222
&ckey=STRING32CHARACTERS11223344556677
&data=71b85c10d2427146dc24007c610ea647823465febf2447560d6fc02a33bd0207
The Measurement API will then decrypt the data payload and set the parameters accordingly.
Encrypting Additional Parameters
You can include all of the parameters in a request to measure a click or impression, but the action, ckey and data parameters will still need to set in the request. If this was the request URL with plain text values for keys:
https://publisher_id.measure.mobileapptracking.com/serve?action=click
&publisher_id=19228
&site_id=2962
&ios_ifa=AAAAAA-BBBB-CCCC-11111-2222222222222
&cost_model=cpc
&cost=0.01
The publisher_id, site_id, ios_ifa cost_model and cost parameters can all be included in the encrypted data payload, resulting in a similar URL below:
https://publisher_id.measure.mobileapptracking.com/serve?action=click
&ckey=STRING32CHARACTERS11223344556677
&data=424365febf2447560d671b85cag130d2427143dfga6d24007c6103ea6478dfga3465febf244760ds6fc02ad3adsf3bd0200sd7jf
Obtaining Your Private API Key
Each request to measure installs and/or events must be authenticated. The authentication method requires a public key (Consumer Key) and signature seeded with a Private Key. These two keys are associated with a user's single API Key.
-
Private Key – This is the Private key that will be used to generate the signature on both the client and server but will not be included in the request.
-
Consumer Key – This is the public key that will be included with each request so the Private Key used to create the signature can be identified.
Note
Please contact your TUNE Sales Engineer to obtain your Consumer Key.
When the Measurement API receives requests, it will use the Consumer Key and Private Key to re-generate the hash and determine if the signature provided with the request matches the re-generated signature. Invalid requests will be rejected.
To obtain your Private Key to send requests to the TUNE APIs:
- Log into your Attribution Analytics account.
- Under the Accounts section, click Users.
- Click on the user you want to add an API key for.
- On the API Keys tab, click Add API Key.
- Optionally provide the IP address of the device that will be using the API key.
- Save를 클릭합니다.
Note
You must have Full access to your Attribution Analytics account to create/edit API keys.
Resource Authentication
Using this Measurement API requires authorization from TUNE for measuring installs and/or events. Once authorized, TUNE will enable you to measure sessions and events with the Measurement API. All requests to measure sessions and events need to be authenticated. Authentication with the Measurement API is documented below.
While partners and advertisers can both use the Measurement API for Ads, measuring the Sessions and Events by advertisers directly with the Measurement API requires a commitment in order to ensure data integrity. If you are an advertiser and would like to use the Measurement API as SDK-less solution, please contact your client success manager for details.
Below outlines how authentication is required by the Measurement API to measure sessions and events in order to verify the requests.
Keys and Parameters
Each request to measure installs and/or events must be authenticated. The authentication method requires a public key (Consumer Key) and signature seeded with a Private Key. These two keys are associated with a user's single API Key.
- Consumer Key – This is the public key that will be included with each request so the Private Key used to create the signature can be identified.
- Private Key – This is the Private key that will be used to generate the signature on both the client and server but will not be included in the request.
To obtain a Private Key, please read Obtaining Your Private API Key.
To obtain a Consumer Key, please contact your TUNE Client Success Manager and they will provide these values.
Accordingly, the requests to measure installs and/or events will need to include these two parameters as headers based on the above keys:
- consumer_key – Consumer Key
- signature – Signature generated by hashing the Private Key with the URL encoded parameters.
When the Measurement API receives requests, it will use the Consumer Key and Private Key to re-generate the hash and determine if the signature provided with the request matches the re-generated signature. Invalid requests will be rejected.
Make a Request with Authentication
Required Values
A request to measure a session or event must include these values:
- Advertiser ID – The ID of your advertiser account we've defined for you.
- Private Key
- Consumer Key
- Method – GET or POST depending on HTTP method used.
- Host – Your Advertiser ID as the sub sub domain at measure.mobileapptracking.com
- Timestamp – Unix GMT timestamp (in seconds)
- Additional Parameters – * discussed below
All requests must be sent via SSL, so use HTTPS.
Measurement Parameters
First you will need to generate a list of parameters for the request. If this were a standard GET request, it would be the query string of the URL.
If your parameters are formatted as a query string, then simply convert them to an array.
action=session&advertiser=877
&site_id=2960
&ios_ifa=aaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee
Example array of parameters using query string above:
$params = array(
action => 'session',
advertiser_id => 877,
site_id => '2960',
ios_ifa => 'aaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'
);
The param array then needs to be converted to a URL-encoded string. Each key-value pair should start with a "& " including the first one.
%26action%3Dsession%26advertiser%3D877%26site_id%3D2960%26ios_ifa%3Daaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee%0A
Required HTTP Headers
The consumer_key, signature, and timestamp values need to be included as HTTP headers in the request rather than being included in the parameters (query string of URL). With Curl in PHP, they would be set with "curl_setopt".
Accordingly, these three values need to be set in the HTTP header of each request.
- mat-consumer-key: 'consumer key'
- mat-signature: 'signature'
- mat-timestamp: 'timestamp'
Generate Signature
The signature is generated with hmac using encryption algorithm SHA256. It is generated with five strings:
- Private Key
- Method – GET or POST depending on HTTP method used.
- Host – Domain name
- URL – The path of the page including query string.
- Timestamp – Unix GMT timestamp (in seconds)
- * URL encoded Params – If doing POST instead of GET need to include URL encoded params. Not set in signatures for GET requests.
예시
Example Python Code
Below is example authentication code written in Python.
import string
import urlparse
import urllib
import time
import requests
def generate_signature(api_key, http_method, host, uri, timestamp, post_params=""):
string_to_sign = http_method + "\n" + host + "\n" + uri + "\n" + timestamp + "\n" + post_params
dig = hmac.new(api_key, msg=string_to_sign, digestmod=hashlib.sha256).digest()
signature = base64.urlsafe_b64encode(dig)
return string.rstrip(signature, '=')
advertiser_id = YOUR_ADVERTISER_ID
api_key = YOUR_PRIVATE_KEY
consumer_key = YOUR_CONSUMER_KEY
method = 'GET'
host = advertiser_id + '.measure.mobileapptracking.com'
path = '/serve'
timestamp = str(int(time.time()))
params = {
'action': 'install',
'advertiser_id': advertiser_id,
'sdk': 'server',
'site_id': '2960',
'ios_ifa': 'AAAAAA-BBBB-CCCC-11111-2222222222222',
'ios_ad_tracking_disabled': '0',
'ios_ifv': 'ZZZZZZ-BBBB-CCCC-11111-2222222222222',
'user_id': '10000000001',
'device_ip': '123.123.123.123',
'device_brand': 'Apple',
'device_model': 'iPhone5,2',
'device_carrier': 'Verizon',
'country_code': 'US',
'response_format': 'json',
}
url = urlparse.urlparse('https://' + host + path + '?' + urllib.urlencode(params))
signature = generate_signature(api_key, 'GET', url.netloc, url.path + '?' + url.query, timestamp)
headers = {
'mat-consumer-key': consumer_key,
'mat-signature': signature,
'mat-timestamp': timestamp,
}
requests.get(url.geturl(), headers=headers)
Example PHP Code
When using PHP, the function to generate the signature value will be different because in the GET the parameters will be included in the URI while with a POST the parameters need to be included separately.
GET Request with PHP
Pseudo code in PHP to measure a session via GET request is below:
<?php
$advertiser_id = YOUR_ADVERTISER_ID;
$api_key = YOUR_PRIVATE_KEY;
$consumer_key = YOUR_CONSUMER_KEY;
$method = 'GET';
$host = $advertiser_id . '.measure.mobileapptracking.com';
$timestamp = UNIX_TIMESTAMP;
$param_str = 'action=session'
. '&advertiser_id=' . $advertiser_id
. '&sdk=server&site_id=2960'
. '&ios_ifa=AAAAAA-BBBB-CCCC-11111-2222222222222'
. '&ios_ad_tracking_disabled=0'
. '&ios_ifv=ZZZZZZ-BBBB-CCCC-11111-2222222222222'
. '&user_id=10000000001'
. '&device_ip=123.123.123.123'
. '&device_brand=Apple'
. '&device_model=iPhone5,2'
. '&device_carrier=Verizon'
. '&country_code=US'
. '&response_format=json' ;
$uri = '/serve?' . $param_str;
$signature = generateSignature( $api_key, $method, $host, $uri, $timestamp );
$options = array(
CURLOPT_HTTPHEADER => array(
'mat-consumer-key: ' . $consumer_key,
'mat-signature: ' . $signature,
'mat-timestamp: ' . $timestamp,
)
);
$ch = curl_init( 'https://' + host + uri );
curl_setopt_array($ch, $options);
curl_exec($ch);
curl_close($ch);
function generateSignature($api_key, $method, $host, $uri, $timestamp) {
$string_to_sign
= $method . "\n" . $host . "\n" . $uri . "\n" . $timestamp . "\n";
$hash = hash_hmac("sha256", $string_to_sign, $api_key, true);
// base64 encode string and then replacing / with _ and + with -
$signature = base64_encode_url_safe($hash);
// strip trailing equal signs
$signature = rtrim($signature, '=');
return $signature;
}
function base64_encode_url_safe($input) {
return strtr(base64_encode($input), '+/', '-_');
}
POST Request with PHP
Pseudo code in PHP to measure a session or event via POST request is below. Note that to generate the signature successfully for a POST request the first key value pair needs to start with & sign.
'session',
advertiser_id => $advertiser_id,
sdk => 'server',
response_format => 'json',
site_id => '2960',
ios_ifa => 'AAAAAA-BBBB-CCCC-11111-2222222222222',
ios_ad_tracking_disabled => '0',
ios_ifv => 'ZZZZZZ-BBBB-CCCC-11111-2222222222222',
user_id => '10000000001',
device_ip => '123.123.123.123',
device_brand => 'Apple',
device_model => 'iPhone5,2',
device_carrier => 'Verizon',
country_code => 'US'
);
$param_str = urlencodeParams( $params );
$uri = '/serve';
$signature = generateSignature($api_key, $method, $host, $uri, $timestamp, $param_str);
$options = array(
CURLOPT_POSTFIELDS => $param_str,
CURLOPT_HTTPHEADER => array(
'mat-consumer-key: ' . $consumer_key,
'mat-signature: ' . $signature,
'mat-timestamp: ' . $timestamp,
)
);
$ch = curl_init( 'https://' + host + uri );
curl_setopt_array($ch, $options);
curl_exec($ch);
curl_close($ch);
function urlencodeParams( $params ) {
ksort( $params ); // Sort parameter key-value pairs by key; else MAT cannot match the signature.
$string = '';
foreach ($params as $key => $value) {
$string .= '&' . $key . '=' . urlencode($value);
}
return $string;
}
function generateSignature($api_key, $method, $host, $uri, $timestamp, $param_str) {
$string_to_sign
= $method . "\n" . $host . "\n" . $uri . "\n" . $timestamp . "\n" . $param_str;
$hash = hash_hmac("sha256", $string_to_sign, $api_key, true);
// base64 encode string and then replacing / with _ and + with -
$signature = base64_encode_url_safe($hash);
// strip trailing equal signs
$signature = rtrim($signature, '=');
return $signature;
}
function base64_encode_url_safe($input) {
return strtr(base64_encode($input), '+/', '-_');
Example GO Code
Below is example authentication code written in GO.
package reqcrypt
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"errors"
"net/http"
"net/url"
"sort"
"strconv"
"strings"
"time"
)
var (
ErrBadHttpRequest = errors.New("unable to create http request")
ErrBadPostData = errors.New("POST parameters may only have single value")
ErrInvalidHttpMethod = errors.New("http method must be GET or POST")
ErrPostParamsOnGet = errors.New("received POST params on GET request")
)
// NewSignedGet creates a signed http GET request.
func NewSignedGet(consumerKey, privateKey, url string) (*http.Request, error) {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, ErrBadHttpRequest
}
// sign with private key
curtime := int32(time.Now().Unix())
curTimeStr := strconv.FormatInt(int64(curtime), 10)
signature, _ := GenerateSignature(privateKey, "GET", req.URL.Host, req.URL.RequestURI(), curTimeStr, "")
addMatHeaders(req, consumerKey, signature, curTimeStr)
return req, nil
}
// NewSignedPost creates a signed http POST request.
func NewSignedPost(consumerKey, privateKey, url string, postVals url.Values) (*http.Request, error) {
// same dance as http.PostForm
req, err := http.NewRequest("POST", url, strings.NewReader(postVals.Encode()))
if err != nil {
return nil, ErrBadHttpRequest
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
// sorted, url-encoded post param string
encodedVals, err := EncodePostParams(postVals)
if err != nil {
return nil, ErrBadPostData
}
// sign with private key
curtime := int32(time.Now().Unix())
curTimeStr := strconv.FormatInt(int64(curtime), 10)
signature, _ := GenerateSignature(privateKey, "POST", req.URL.Host, req.URL.RequestURI(), curTimeStr, encodedVals)
addMatHeaders(req, consumerKey, signature, curTimeStr)
return req, nil
}
func addMatHeaders(req *http.Request, consumerKey, signature, curTimeStr string) {
req.Header.Add("mat-consumer-key", consumerKey)
req.Header.Add("mat-signature", signature)
req.Header.Add("mat-timestamp", curTimeStr)
}
// EncodePostParams serializes POST parameters to a string for signing.
func EncodePostParams(postVals url.Values) (string, error) {
// sorted keys
keys := make([]string, 0, len(postVals))
for k := range postVals {
keys = append(keys, k)
}
sort.Strings(keys)
// emit url-encoded string in sorted key order
var buf bytes.Buffer
for _, k := range keys {
// exactly one value for each POST arg
curData := postVals[k]
if len(curData) != 1 {
return "", ErrBadPostData
}
buf.WriteByte('&')
buf.WriteString(k)
buf.WriteByte('=')
buf.WriteString(url.QueryEscape(curData[0]))
}
return buf.String(), nil
}
// GenerateSignature computes a signature hash according to the MAT server-to-server auth spec.
func GenerateSignature(privateKey, httpMethod, httpHost, uri, timestampStr, postParams string) (string, error) {
// sanity checks
if httpMethod != "GET" && httpMethod != "POST" {
return "", ErrInvalidHttpMethod
}
// conservative buffer size to avoid reallocs
const numNewLines = 5
bufSize := len(httpMethod) + len(httpHost) + len(uri) + len(timestampStr) + len(postParams) + numNewLines
// string-to-sign
buf := bytes.NewBuffer(make([]byte, 0, bufSize))
buf.WriteString(httpMethod)
buf.WriteByte('\n')
buf.WriteString(httpHost)
buf.WriteByte('\n')
buf.WriteString(uri)
buf.WriteByte('\n')
buf.WriteString(timestampStr)
buf.WriteByte('\n')
buf.WriteString(postParams)
// sign with private key
mac := hmac.New(sha256.New, []byte(privateKey))
mac.Write(buf.Bytes())
// strip padding from base64 encoding
paddedSig := base64.URLEncoding.EncodeToString(mac.Sum(nil))
return strings.TrimRight(paddedSig, "="), nil
}
Use the code below to test your authentication in GO.
package reqcrypt
import (
"net/url"
"strconv"
"testing"
"time"
)
var signatureTests = []struct {
privateKey string
consumerKey string
timestampStr string
httpMethod string
httpHost string
uri string
postVals url.Values
truthSignature string
}{
// GET
{"adv1", "18d84eb30b59b5f3cc748bfe9f68b472", "1406146778",
"GET", "engine.mobileapptracking.com", "/serve", make(url.Values),
"ur3aUlwbRXGcxxt0EvDa2BQTqkCUjb4RdHww1S5EAWY"},
// POST with params
{"adv1", "18d84eb30b59b5f3cc748bfe9f68b472", "1406146778",
"POST", "engine.mobileapptracking.com", "/serve", url.Values{"var1": []string{"blue"}},
"X5wxZPS_s5941_d_wnaUcS1Qgd1jZvu94jv5aImtaxo"},
// POST with params requiring url encoding and key sort
{"adv1", "18d84eb30b59b5f3cc748bfe9f68b472", "1406146778",
"POST", "engine.mobileapptracking.com", "/serve",
url.Values{"var1": []string{"blue"}, "meow": []string{"+-="}, "alpha": []string{"beta"}},
"_2fqNArAgJO3vvtE0ff3XZ3mYSsnIbu5Ynkaw-S-o-c"},
}
func TestGenerateSignature(t *testing.T) {
for tidx, tcase := range signatureTests {
// encode POST query params if needed
postParams := ""
if tcase.httpMethod == "POST" {
var err error
postParams, err = EncodePostParams(tcase.postVals)
if err != nil {
t.Errorf("case: %d, non-empty error: %s", tidx, err)
}
}
// generate signature
testSignature, testError := GenerateSignature(tcase.privateKey, tcase.httpMethod,
tcase.httpHost, tcase.uri, tcase.timestampStr, postParams)
// validate signature against truth data
if testSignature != tcase.truthSignature {
t.Errorf("case: %d, received=%s, wanted=%s", tidx, testSignature, tcase.truthSignature)
}
if testError != nil {
t.Errorf("case: %d, non-empty error: %s", tidx, testError)
}
}
}
func BenchmarkNewSignedGet(b *testing.B) {
consumerKey := "publickey"
privateKey := "privatekey"
u := "http://123213.measurement.mobileapptracking.com/collect/" +
"click?pub_id=123&woof_id=22&campaign_id=5&payout=1000"
for n := 0; n < b.N; n++ {
NewSignedGet(consumerKey, privateKey, u)
}
}
func BenchmarkNewSignedPost(b *testing.B) {
consumerKey := "publickey"
privateKey := "privatekey"
u := "http://123213.measurement.mobileapptracking.com/collect/"
params := make(url.Values)
params.Add("pub_id", "123")
params.Add("woof_id", "22")
params.Add("campaign_id", "5")
params.Add("payout", "1000")
for n := 0; n < b.N; n++ {
NewSignedPost(consumerKey, privateKey, u, params)
}
}
func BenchmarkGenerateSignature(b *testing.B) {
u := "http://123213.measurement.mobileapptracking.com/collect/" +
"click?pub_id=123&woof_id=22&campaign_id=5&payout=1000"
h := "123213.measurement.mobileapptracking.com"
curtime := int32(time.Now().Unix())
curTimeStr := strconv.FormatInt(int64(curtime), 10)
for n := 0; n < b.N; n++ {
GenerateSignature("privatekey", "GET", h, u, curTimeStr, "")
}
}
Example Java Code
Below is example authentication code written in Java.
import com.mashape.unirest.http.JsonNode;
import com.mashape.unirest.http.Unirest;
import com.mashape.unirest.http.exceptions.UnirestException;
import com.mashape.unirest.http.utils.URLParamEncoder;
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import java.util.LinkedHashMap;
public class Authentication {
private static final String HMAC_SHA256_ALGORITHM = "HmacSHA256";
private static final String ADVERTISER_ID = "YOUR_ADVERTISER_ID";
private static final String SITE_ID = "YOUR_SITE_ID";
private static final String API_KEY = "YOUR_PRIVATE_KEY";
private static final String CONSUMER_KEY = "YOUR_CONSUMER_KEY";
private static final String HOST = ADVERTISER_ID + ".measure.mobileapptracking.com";
private static final String PATH = "/serve";
public static void main(String[] args) throws UnirestException, NoSuchAlgorithmException, InvalidKeyException,
UnsupportedEncodingException {
System.out.println(getResponseBody(encodeUrlParams(ADVERTISER_ID, SITE_ID)));
System.out.println(postResponseBody(encodeUrlParams(ADVERTISER_ID, SITE_ID)));
}
public static JsonNode getResponseBody(String encodedUrlParams) throws UnirestException,
InvalidKeyException, NoSuchAlgorithmException, UnsupportedEncodingException {
String httpMethod = "GET";
String timestamp = String.valueOf(new Date().getTime());
String url = "https://" + HOST + PATH + "?" + encodedUrlParams;
String stringToSign = createStringToSign(httpMethod, HOST, PATH, encodedUrlParams, timestamp, "");
return Unirest.get(url)
.header("mat-consumer-key", CONSUMER_KEY)
.header("mat-signature", generateSignature(stringToSign, API_KEY))
.header("mat-timestamp", timestamp)
.asJson()
.getBody();
}
public static JsonNode postResponseBody(String encodedUrlParams) throws UnirestException, InvalidKeyException,
NoSuchAlgorithmException, UnsupportedEncodingException {
String httpMethod = "POST";
String timestamp = String.valueOf(new Date().getTime());
String url = "https://" + HOST + PATH;
String stringToSign = createStringToSign(httpMethod, HOST, PATH, encodedUrlParams, timestamp, encodedUrlParams);
return Unirest.post(url)
.header("Content-Type", "application/x-www-form-urlencoded")
.header("mat-consumer-key", CONSUMER_KEY)
.header("mat-signature", generateSignature(stringToSign, API_KEY))
.header("mat-timestamp", timestamp)
.body(encodedUrlParams)
.asJson()
.getBody();
}
private static String encodeUrlParams(String advertiserId, String siteId) {
LinkedHashMap<String, String> params = new LinkedHashMap<>();
params.put("action", "install");
params.put("advertiser_id", advertiserId);
params.put("country_code", "US");
params.put("device_brand", "Apple");
params.put("device_carrier", "Verizon");
params.put("device_ip", "123.123.123.123");
params.put("device_model", "iPhone5,2");
params.put("ios_ad_tracking_disabled", "0");
params.put("ios_ifa", "AAAAAA-BBBB-CCCC-11111-2222222222222");
params.put("ios_ifv", "ZZZZZZ-BBBB-CCCC-11111-2222222222222");
params.put("response_format", "json");
params.put("sdk", "server");
params.put("site_id", siteId);
params.put("user_id", "10000000001");
return "&" + params.entrySet().stream()
.map(p -> URLParamEncoder.encode(p.getKey()) + "=" + URLParamEncoder.encode(p.getValue()))
.reduce((p1, p2) -> p1 + "&" + p2)
.orElse("");
}
private static String createStringToSign(String httpMethod,
String host,
String path,
String encodedUrlParams,
String timestamp, String postParams) {
return httpMethod.equals("GET") ?
httpMethod + "\n" + host + "\n" + path + "?" + encodedUrlParams + "\n" + timestamp + "\n" + postParams :
httpMethod + "\n" + host + "\n" + path + "\n" + timestamp + "\n" + postParams;
}
private static String generateSignature(String urlStringToSign, String key) throws NoSuchAlgorithmException,
InvalidKeyException, UnsupportedEncodingException {
Mac sha256_HMAC = Mac.getInstance(HMAC_SHA256_ALGORITHM);
SecretKeySpec secret_key = new SecretKeySpec(key.getBytes(), HMAC_SHA256_ALGORITHM);
sha256_HMAC.init(secret_key);
return Base64.encodeBase64URLSafeString(sha256_HMAC.doFinal(urlStringToSign.getBytes()));
}
}
Request Responses
The Measurement API returns response in JSON format. Important parameters in the response are:
- success - Indicates if request was successful or not.
- log_id - The ID of the log. If event type was install then it would be the Log Install ID
- message - Array of errors if request was unsucessful.
Success Response
A successful request will return response like:
{
"success": true,
"site_event_type": "install",
"tracking_id": "3c225547069f297b3a0ed4264aac36b3",
"advertiser_ref_id": null,
"log_id": "b2f3ce90d42e12e895-20140709-877",
"message": [ ],
"log_action": true
}
Unsuccessful Response
A bad / invalid request will return the success parameter as false and there will be an array string in the errors parameter. One of these unsuccessful response will be like:
{
"success": false,
"site_event_type": "install",
"tracking_id": "b34ce0f6d1578958972c30b6a35882fc",
"advertiser_ref_id": null,
"log_id": "7d6d2f08db789514af-20140709-877",
"message": [
"Duplicate request detected."
]
}
Updated 8 months ago