Important: Please carefully choose the authentication method based on your use case. Choosing the wrong method can lead to significant security risks (access violation, data leakage, etc.) . Please take a look at our recommendations to choose the most secure way.
Choosing the authentication method
Summary of secure authentication methods to be used depending on use case:
Use case
Secure authentication method
Direct servers integration
Live data collection from mobile applications
Please ensure to use the right secure authentication method to avoid any security risks like access violation, data leakage, etc. For example, if you use the long-term API token into your mobile application, it could leak by decompiling the application.
Get in touch with your Account manager if you want further information on the best authentication method to pick for your needs.
Long-term API tokens
Long-term API tokens are the way to go if you plan to make server-to-server calls.
We recommend creating "technical users" to bear long-term API tokens, in order to avoid cancelling your token along with deleting a regular user.
A long-term API token identifies a user but it has a longer expiration delay (usually from 12 to 24 months).
A user can have multiple long-term tokens, which can be useful to manage tokens renewal: just create a new API token before the old one expires and update your app.
There are 2 ways you can create a long-term API token:
In the Navigator application
Using the API
Once generated you can use the API token in the Authorization header of all your requests.
List and create tokens in the UI
Head to Navigator
Navigate to settings
Then My Account > API Tokens
Click on the "New API Token" to generate a long-term API token
Please note that you need an other API token to generate long-term API tokens using the API. You can generate the first one in the Navigator UI.
You won't be able to see the complete value of the tokens using this API.
Signature authentication
The Signature authentication scheme is used in an environment where the calling part operates in a public network or published mobile app, and the communication with the mediarithmics platform could be hacked by a man in the middle or the app decompiled. It is the preferred choice for mobile app tracking.
Signature authentication is limited to 2 actions only:
Posting activities using the POST /v1/datamarts/${datamart_id}/user_activities endpoint
Getting user segments by user account ID, email hash or user agent ID (not user point ID) using GET /v1/datamarts/:datamartId/user_points/compartmentId=:compartmentId,user_account_id=:userAccountId/user_segments
, or GET /v1/datamarts/:datamartId/user_points/email_hash=:emailHash/user_segments or GET /v1/datamarts/:datamartId/user_points/user_agent_id=:userAgentId/user_segments endpoints
The authentication of the calling party (the mobile app) is done through a signature (Message Authentication Code) using a hashing algorithm (HMAC-SHA256).
Signature authentication is linked to a mediarithmics user. As for the API token approach, we recommend you create a technical user on mediarithmics to bear this authentication scheme.
The scheme associated with the secret key. Possible values : HMAC_SHA256
key_id
string
A unique string identifying your secret key. The API will return an error if the value is already used by another secret key. This value will be used later on when signing API calls.
Response Body
field
type
description
id
String
Record Id
user_id
String
Id of the User linked to the secret key
creation_ts
String
Date of creation
expiration_ts
String
Date of expiration
key_id
String
A unique string identifying your secret key. Provided in the request by the user.
scheme
String enum
The scheme associated with the secret key. Possible values: HMAC_SHA256
secret_key
String enum
The value of the secret key. It has a length of 16 bytes and is provided as a hexadecimal string Warning: Store this value as the value of the secret key is only visible in the response of the creation request. In all subsequent calls, it will be obfuscated.
Usage
For each API call, a digest of the message should be calculated. Then, the 3 following headers should be contained in the API call:
X-Mics-Mac: the digest of the input data (Message Authentication Code)
X-Mics-Key-Id: the key_id of the secret key (provided by you when creating a new secret key)
X-Mics-Ts: the timestamp used in the digest (in ms)
Digest calculation
We recommend you find a library that generates digests for your platform. This library will need 2 input in order to work:
A key. You should use the secret_key provided by the platform.
A message to sign. you should generate the message from the info contained in the API call payload (see below).
The digest should be presented as a Base64 string.
Message generation
The message to sign should be generated from the following values, in this order, separated by a \n (e.g. ${uri}\n${key_id}\n${timestamp}\n${request_body}):
The uri of the API method used
The key_id value (provided by you when creating the secret key)
A timestamp (ms) generated at the time of the call
Therefore, the calculation of the HMAC-SHA256 of this message, with the secret key 846cee8e-5558-4ca0-b723-095aa043c6ee, base64 encoded, will be rwhKdaWtw5Hx3zjcrZDv7eO4fyNbBkIfsh2PjI+BiRE=.
Posting activities for iOS using Swift
import Foundation
import CryptoKit
func signPayload(uri:String, secretKeyId:String, timestamp:Int64, payload:String, secretKeyValue:String) -> String {
let message = Data("\(uri)\n\(secretKeyId)\n\(String(timestamp))\n\(payload)".utf8)
let symmetricKey = SymmetricKey(data: Data(secretKeyValue.utf8))
let hmac = HMAC<SHA256>.authenticationCode(for: message, using: symmetricKey)
let base64hmac = Data(hmac).base64EncodedString()
return base64hmac
}
func sendUserEvent(
userAccountId:String?,
mobileVendorId:String?,
mobileAdId:String?,
eventName:String,
category:String?,
subCategory:String?) {
// Constant parameters that could be defined in a configuration file
let DATAMART_ID="XXX" // the id of the database
let APPLICATION_ID="XXX" // the id of the mobile application
let COMPARTMENT_ID="XXX" // the id used to define the nature of the user account id
let VENDOR_ID_REGISTRY_ID="XXX" // the id used to define the nature of the mobile vendor id
let SECRET_KEY_ID="XXX" // the id of the secret key used to sign the message
let SECRET_KEY_VALUE="ZZZZZZZZZZZZZZZZZZZZZZZZZZ" // the secret key value
// The URL entry is static
let uri = "/v1/datamarts/"+DATAMART_ID+"/user_activities"
let url = "https://api.mediarithmics.com" + uri
let timestamp = Int64(Date().timeIntervalSince1970 * 1000)
// Prepare the json payload
var identifiers:[[String:Any]] = []
if let getUserAccountId = userAccountId { identifiers.append(["$type":"USER_ACCOUNT", "$compartment_id":COMPARTMENT_ID, "$user_account_id": getUserAccountId ]) }
if let getMobileVendorId = mobileVendorId { identifiers.append(["$type":"USER_AGENT", "$user_agent_id": ("mov:ios:" + VENDOR_ID_REGISTRY_ID + ":" + getMobileVendorId) ]) }
if let getMobileAdId = mobileAdId { identifiers.append(["$type":"USER_AGENT", "$user_agent_id": ("mob:ios:raw:" + getMobileAdId) ]) }
let jsonPayload: [String: Any] = [
"$type": "APP_VISIT",
"$app_id": APPLICATION_ID,
"$session_status": "IN_SESSION",
"$ts" : timestamp,
"$user_identifiers" : identifiers,
"$events":[[
"$event_name" : eventName,
"$ts" : timestamp,
"$properties" : [
"category" : category ?? nil,
"sub_category" : subCategory ?? nil ]]]]
// Create a URLRequest with the specified URL
var request = URLRequest(url: URL(string:url)!)
do {
// Set the HTTP method
request.httpMethod = "POST"
// Convert the dictionary to JSON data
let jsonData = try JSONSerialization.data(withJSONObject: jsonPayload, options: [])
request.httpBody = jsonData
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
// Build the HMAC signature
let base64hmac = signPayload(uri:uri, secretKeyId: SECRET_KEY_ID, timestamp: timestamp, payload: String(data:jsonData, encoding: .utf8)!, secretKeyValue: SECRET_KEY_VALUE)
// Set the specific headers
request.setValue(SECRET_KEY_ID, forHTTPHeaderField: "X-Mics-Key-Id")
request.setValue(String(timestamp), forHTTPHeaderField: "X-Mics-Ts")
request.setValue(base64hmac, forHTTPHeaderField: "X-Mics-Mac")
} catch {
print("Error while building the request data: \(error)")
return
}
// Create a URLSession task to perform the API call
let task = URLSession.shared.dataTask(with: request) { data, response, error in
// Check for errors
if let error = error {
print("Error: \(error)")
return
}
// Check for a valid HTTP response
guard let httpResponse = response as? HTTPURLResponse else {
print("Invalid HTTP response")
return
}
// Check for a successful status code (e.g., 200 OK)
if httpResponse.statusCode == 200 {
let responseDataString = data.flatMap { String(data: $0, encoding: .utf8) } ?? "No response data"
print("HTTP Response OK!, body:\(responseDataString) ")
} else {
let responseDataString = data.flatMap { String(data: $0, encoding: .utf8) } ?? "No response data"
print("HTTP Response Error: \(httpResponse.statusCode)\n\(responseDataString)")
return
}
}
// Start the URLSession task
task.resume()
}
Posting activities for Android using Kotlin
import java.io.DataOutputStream
import java.net.HttpURLConnection
import java.net.URL
import java.util.*
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
fun signPayload(uri: String, secretKeyId: String, timestamp: Long, payload: String, secretKeyValue: String): String {
val message = "$uri\n$secretKeyId\n$timestamp\n$payload".toByteArray(Charsets.UTF_8)
val secretKey = SecretKeySpec(secretKeyValue.toByteArray(Charsets.UTF_8), "HmacSHA256")
val hmac = Mac.getInstance("HmacSHA256").apply { init(secretKey) }.doFinal(message)
return Base64.getEncoder().encodeToString(hmac)
}
fun sendUserEvent(
userAccountId: String?,
userVendorId: String?,
userMobileAdId: String?,
eventName: String,
category: String?,
subCategory: String?
) {
// Constant parameters that could be defined in a configuration file
val DATAMART_ID = "XXX" // the id of the database
val APPLICATION_ID = "XXX" // the id of the mobile application
val COMPARTMENT_ID = "XXX" // the id used to define the nature of the user account id
val SECRET_KEY_ID = "XXXX" // the id of the secret key used to sign the message
val SECRET_KEY_VALUE = "ZZZZZZZZ" // the secret key value
// The URL entry is static
val uri = "/v1/datamarts/$DATAMART_ID/user_activities"
val url = "https://api.mediarithmics.com$uri"
val timestamp = System.currentTimeMillis()
// Prepare the JSON payload
val identifiers = mutableListOf<String>()
userAccountId?.let { it -> identifiers.add(""" { "${'$'}type" : "USER_ACCOUNT" , "${'$'}compartment_id" : "$COMPARTMENT_ID", "${'$'}user_account_id" : "$it" }""") }
val jsonPayload = """{
"${'$'}type" : "APP_VISIT",
"${'$'}app_id" : "$APPLICATION_ID",
"${'$'}session_status" : "IN_SESSION",
"${'$'}ts" : $timestamp,
"${'$'}user_identifiers" : ${identifiers.toList().joinToString(",", "[", "]")},
"${'$'}events" : [
{
"${'$'}event_name" : "$eventName",
"${'$'}ts" : $timestamp,
"${'$'}properties" : {
"category" : ${if (category == null) "null" else "\"${category}\""},
"sub_category" : ${if (subCategory == null) "null" else "\"${subCategory}\""}
}
}
]
}"""
// Create a HttpURLConnection with the specified URL
val urlConnection = URL(url).openConnection() as HttpURLConnection
try {
// Set the HTTP method
urlConnection.requestMethod = "POST"
// Enable output streams
urlConnection.doOutput = true
urlConnection.doInput = true
// Convert the map to JSON data
val jsonData = jsonPayload.toString().toByteArray(Charsets.UTF_8)
urlConnection.setRequestProperty("charset", "utf-8")
urlConnection.setRequestProperty("Content-Type", "application/json")
urlConnection.setRequestProperty("Content-length", jsonData.size.toString())
println(jsonData.toString(Charsets.UTF_8))
// Build the HMAC signature
val base64hmac = signPayload(uri, SECRET_KEY_ID, timestamp, String(jsonData, Charsets.UTF_8), SECRET_KEY_VALUE)
// Set the specific headers
urlConnection.setRequestProperty("X-Mics-Key-Id", SECRET_KEY_ID)
urlConnection.setRequestProperty("X-Mics-Ts", timestamp.toString())
urlConnection.setRequestProperty("X-Mics-Mac", base64hmac)
//urlConnection.disconnect()
val outputStream: DataOutputStream = DataOutputStream(urlConnection.outputStream)
outputStream.write(jsonData)
outputStream.flush()
} catch (e: Exception) {
println("Error while building the request data: $e")
return
}
// Perform the API call
try {
val responseCode = urlConnection.responseCode
// Check for a successful status code (e.g., 200 OK)
if (responseCode == 200) {
val responseDataString = urlConnection.inputStream.bufferedReader().use { it.readText() }
println("HTTP Response OK!, body: $responseDataString")
} else {
val responseDataString = urlConnection.errorStream.bufferedReader().use { it.readText() }
println("HTTP Response Error: $responseCode\n$responseDataString")
}
} catch (e: Exception) {
println("Error: $e")
} finally {
// Disconnect after the request is complete
urlConnection.disconnect()
}
}
Posting activities in Node.js
var rp = require('request-promise');
var crypto = require('crypto');
var datamart_id = 854;
var app_id = "154"; // string only
var domain = `https://api.mediarithmics.com`
var uri = `/v1/datamarts/${datamart_id}/user_activities`;
var secret_key = "846cee8e-5558-4ca0-b723-095aa043c6ee";
var key_id = "my_key_identifier";
var ts = 1499103950000; // in milliseconds
var test_device = {
"os": "and",
"aaid": "b9f47db8-c0e9-40f4-8aed-fb063d7e54f7"
};
var body = {
"$ts": ts,
"$type": "APP_VISIT",
"$session_status": "IN_SESSION",
"$user_agent_id": `mob:${test_device.os}:raw:${test_device.aaid}`,
"$app_id": app_id,
"$events": [{
"$ts": ts,
"$event_name": "my_super_event",
"$properties": {
"custom_property1": "value1",
"custom_property2": "value2",
"custom_property3": "value3",
"custom_property4": "value4",
"custom_property5": "value5"
}
}]
};
var headers = {
"X-Mics-Key-Id": key_id,
"X-Mics-Ts": ts
};
var message = uri + "\n" + key_id + "\n" + JSON.stringify(ts) + "\n" + JSON.stringify(body);
var signature = signKey(secret_key, message);
headers["X-Mics-Mac"] = signature;
console.log(`uri: ${uri}\nsecret_key: ${secret_key}\nkey_id: ${key_id}\nts: ${JSON.stringify(ts)}\nheaders: ${JSON.stringify(headers)}\nbody: ${JSON.stringify(body)}`)
console.log(`message: ${message}`);
console.log(`signature: ${signature}`);
postEvent(domain + uri,'POST',headers,body);
function signKey(clientKey, message) {
var hash = crypto.createHmac('sha256', clientKey).update(message).digest('base64');
return hash;
};
async function postEvent(uri, method, headers, body) {
var request = await rp({
uri: uri,
method: method,
headers: headers,
body: body,
json: true
});
console.log(request)
}
Getting user segments in Node.js
var rp = require('request-promise');
var crypto = require('crypto');
var datamart_id = 854;
var user_agent_id = 'vec:xxx';
var domain = `https://api.mediarithmics.com`;
var uri = `/v1/datamarts/${datamart_id}/user_points/user_agent_id=${user_agent_id}/user_segments`;
var secret_key = "846cee8e-5558-4ca0-b723-095aa043c6ee";
var key_id = "my_key_identifier";
var ts = 1499103950000; // in milliseconds
var headers = {
"X-Mics-Key-Id": key_id,
"X-Mics-Ts": ts
};
var message = uri + "\n" + key_id + "\n" + JSON.stringify(ts);
var signature = signKey(secret_key, message);
headers["X-Mics-Mac"] = signature;
console.log(`uri: ${uri}\nsecret_key: ${secret_key}\nkey_id: ${key_id}\nts: ${JSON.stringify(ts)}\nheaders: ${JSON.stringify(headers)}`)
console.log(`message: ${message}`);
console.log(`signature: ${signature}`);
getEvent(domain + uri,'GET',headers);
function signKey(clientKey, message) {
var hash = crypto.createHmac('sha256', clientKey).update(message).digest('base64');
return hash;
};
async function getEvent(uri, method, headers) {
var request = await rp({
uri: uri,
method: method,
headers: headers,
json: true
});
console.log(request)
}