Server to Server (S2S) Integration

As an alternative to the Singular SDK, which has to be embedded inside your apps, Singular also provides a REST API that can be used to build a full integration that runs from your own server.

Note: Server-to-server integration is only available to Enterprise customers.

Notes:

  • Any call to the REST API has to include your SDK API key. To retrieve it, log into Singular, go to Settings > SDK Keys, and copy the API Key.

    screenshot-2020.06.16-14_04_15.png

  • Rate limiting is performed in our queue manager after the API call is accepted, so there is no need to deal with the complexity of rate-limiting backpressure.
  • Status codes: The HTTP status code in the response will be 200 for a successful call. Any other code (4xx or 5xx) means the call should be retried.

S2S Integration API Endpoints

Use these definitions in conjunction with "Additional Features" to ensure your REST APIs are working correctly for the desired features. Pay attention to case sensitivity and character encodings where appropriate.

Session Notification Endpoint

Use this endpoint to report any session of your application. It is recommended this is tied to the app open for your application.

https://s2s.singular.net/api/v1/launch?

Method: GET

Note:

  • Sessions should be reported with minimal delay.
  • At least one device identifier is required, (for example, 'idfa').
  • Collection of all the necessary data has some caveats like waiting for asynchronous functions to return and handling various edge-cases, failing to handle these cases appropriately may cause missing data and partial attribution.

Required Session Parameters

Parameter Description Supported Platforms Example
a Singular API Key. iOS, Android your_org_name_sh868sdjv
p Platform Android or iOS. iOS, Android Android
i Package Name (Android) or Bundle ID (iOS) of your application. iOS, Android com.singular.app
ip The IP of the device. iOS, Android 172.58.29.235
ve OS Version of the device at session time. iOS, Android 9.2
ma Make of the device hardware, typically the consumer-facing name (e.g., Samsung, LG, Apple). This parameter must be used with the model parameter. iOS, Android samsung
mo Model of the device hardware (e.g., iPhone 4S, Galaxy SIII). This parameter must be used with the make parameter. iOS, Android SM-G935F
lc The IETF local tag for the device, using two-letter language and country code separated by an underscore. iOS, Android en_US
bd Build of the device, URL encoded iOS, Android Build%2F13D15
openuri If app opened via deep link/Universal Link/app link, the encoded deep link URL value iOS, Android myapp%3A%2F%2Fhome%2Fpage%3 Fqueryparam1%3D value1%26queryparam2%3 Dvalue2
idfa For iOS apps only. Upper-case raw advertising ID with dashes. iOS DFC5A647-9043-4699-B2A5-76F03A97064B
idfv For iOS apps only. Upper-case raw IdentifierForVendor with dashes. iOS 21DB6612-09B3-4ECC-84AC-B353B0AF1334
aifa For Android apps only. Lower-case raw advertising ID with dashes. Android 8ecd7512-2864-440c-93f3-a3cabe62525b
andi For Android apps only. Lower-case raw android ID. Required only when Android Advertising ID is not available on the device. Android fc8d449516de0dfb
dnt Pass 1 if do not track is enabled, 0 if do not track is disabled. iOS, Android 1
app_v App version iOS, Android 1.2.3
install_source Install source package name on Android e.g: 'com.android.vending'. To retrieve this value use PackageManager.getInstallerPackageName Android com.vending.android
install_receipt The receipt received from the install. Learn how to retrieve it at iOS Install Receipt iOS MIISqwYJKoZI...cNqts0jvcNvPcK7 yuj0KhJ9nTTQ54kDKfReihzc6aw==
install Install flag. 'true' if the session was the first after installing the app. 'false' otherwise. Required for Reinstall tracking capabilities. iOS, Android false
install_time The time of the first app install as UNIX time. To retrieve this value use the link on the platform. iOS, Android 1510040127
update_time The time of the last app update as UNIX time.To retrieve this value use the link on the platform. iOS, Android 1510040127
ddl_enabled Deferred deep link flag. 'true' if the server expects a deferred deep link URL to be returned. 'false' otherwise. See our section on deferred deep linking for more details iOS, Android true

Optional Session Parameters

Parameter Description Supported Platforms Example
custom_user_id User ID iOS, Android  123456789abcd
n Human-readable name of the application as displayed in the UI. iOS, Android MyCoolApp
utime Time of the session in UNIX time. iOS, Android 1483228800
umilisec Time of the session in milliseconds UNIX time. iOS, Android 1483228800000
dntoff Pass 0 if do not track is enabled, 1 if do not track is disabled. iOS, Android 0
c Connection type 'wifi' or 'carrier'. iOS, Android wifi
cn Carrier name of the internet provider. iOS, Android Comcast
use_ip Extract the IP field from the HTTP request instead of the 'ip field. Don't use this with the ip parameter iOS, Android false
fcm Firebase Cloud Messaging Device Token. Required for Android Uninstall Tracking Android bk3RNwTe3H0CI2k_ HHwgIpoDKCIZvvD MExUdFQ3P1
gcm Google Cloud Messaging Device Token. Required for Android Uninstall Tracking (legacy) Android bk3RNwTe3H0CI2k_ HHwgIpoDKCIZvvDM ExUdFQ3P1
apns_token Apple Push Notification Service Device Token. Required for iOS Uninstall Tracking iOS b0adf7c9730763f88e1 a048e28c68 a9f806ed032fb522deb ff5bfba010a9b052

Sample Session Request

Python
import requests
      import json

      API_KEY = 'api_key_from_sdk_page'
      LAUNCH_URL = 'https://s2s.singular.net/api/v1/launch'

      params = {
          'a': API_KEY,
          'p': 'Android',
          'i': 'com.singular.app',
          'ip': '10.1.2.3',
          've': '9.2',
          'ma': 'samsung',
          'mo': 'SM-G935F',
          'lc': 'en_US',
          'aifa': '8ecd7512-2864-440c-93f3-a3cabe62525b',
          'andi': 'fc8d449516de0dfb',
          'utime': 1483228800,
          'dnt': 0,
          'install':'true',
          'n': 'MyCoolApp',
          'c': 'wifi',
          'cn': 'Comcast',
          'bd': 'Build/13D15',
          'fcm':'bk3RNwTe3H0CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1',
          'app_v':'1.2.3',
          'openuri':'myapp%3A%2F%2Fhome%2Fpage%3Fqueryparam1%3Dvalue1',
          'ddl_enabled':'false',
          'install_source': 'com.android.vending',
          'install_time': 1510040127,
          'update_time': 1510090877,
'custom_user_id': '123456789abcd' } result = requests.get(LAUNCH_URL, params=params) print result.json()
HTTP
https://s2s.singular.net/api/v1/launch?aifa=8ecd7512-2864-440c-93f3-a3cabe62525b&andi=fc8d449516de0dfb&p=Android&a=API_KEY&i=com.singular.app&ip=10.1.2.3&ve=9.2&dnt=0&n=MyCoolApp&dnt=0&c=wifi&cn=Comcast&lc=en_US&bd=Build%2FMMB29K&ma=samsung&mo=SM-G935F&custom_user_id=123456789abcd
cURL
#GET
curl --request GET \
  --url 'https://s2s.singular.net/api/v1/launch?aifa=8ecd7512-2864-440c-93f3-a3cabe62525b&andi=fc8d449516de0dfb&p=Android&a=API_KEY&i=com.singular.app&ip=10.1.2.3&ve=9.2&dnt=0&n=MyCoolApp&dnt=0&c=wifi&cn=Comcast&lc=en_US&bd=Build%2FMMB29K&ma=samsung&mo=SM-G935F&custom_user_id=123456789abcd'
Java
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.LinkedHashMap;
import java.util.Map;
import org.json.JSONObject; //From java-json.jar

public class SingularSessionEvent {
 public static void main(String[] args) {
  try {
   SingularSessionEvent.postEvent();
  } catch (Exception e) {
   e.printStackTrace();
  }
 }

 public static void postEvent() throws Exception {
  String singularAPIKey = "api_key_from_sdk_page";
  String singularSessionEndpoint = "https://s2s.singular.net/api/v1/launch";

  Map < String, Object > params = new LinkedHashMap < > ();
  params.put("a", singularAPIKey);
  params.put("p", "Android");
  params.put("i", "com.singular.app");
  params.put("ip", "10.1.2.3");
  params.put("ve", "9");
  params.put("ma", "samsung");
  params.put("mo", "SM-A705MN");
  params.put("lc", "en_US");
  params.put("aifa", "8ecd7512-2864-440c-93f3-a3cabe62525b");
  params.put("andi", "fc8d449516de0dfb");
  params.put("utime", 1483228800);
  params.put("dnt", 0);
  params.put("install", "true");
  params.put("n", "MyCoolApp");
  params.put("bd", "Build/13D15");
  params.put("fcm", "bk3RNwTe3H0CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1");
  params.put("app_v", "1.2.3");
  params.put("openuri", "myapp%3A%2F%2Fhome%2Fpage%3Fqueryparam1%3Dvalue1");
  params.put("ddl_enabled", "false");
  params.put("install_source", "com.android.vending");
  params.put("install_time", 1510040127);
  params.put("update_time", 1510090877);
params.put("custom_user_id", "123456789abcd");

URL url = new URL(singularSessionEndpoint); StringBuilder postData = new StringBuilder(); for (Map.Entry < String, Object > param: params.entrySet()) { if (postData.length() != 0) postData.append('&'); postData.append(URLEncoder.encode(param.getKey(), "UTF-8")); postData.append('='); postData.append(URLEncoder.encode(String.valueOf(param.getValue()), "UTF-8")); } byte[] postDataBytes = postData.toString().getBytes("UTF-8"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("POST"); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); conn.setRequestProperty("Content-Length", String.valueOf(postDataBytes.length)); conn.setDoOutput(true); conn.getOutputStream().write(postDataBytes); Reader in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8")); StringBuilder sb = new StringBuilder(); for (int c; (c = in .read()) >= 0;) sb.append((char) c); // Print Singular JSON Response String response = sb.toString(); JSONObject singularResponse = new JSONObject(response.toString()); if (singularResponse.length() > 1) { System.out.println("status: " + singularResponse.getString("status")); System.out.println("reason: " + singularResponse.getString("reason")); // Handle Reason } else { System.out.println("status: " + singularResponse.getString("status")); } } }

Event Notification Endpoint

Use this endpoint to report any post-session event of your application.

https://s2s.singular.net/api/v1/evt?

Method: GET

Required Event Parameters

Parameter Description Supported Platforms Example
n Name of the event. iOS, Android ViewItem  
a Singular API Key. iOS, Android a42be1d8119389dd36c7 acbeaf6abcd8  
p Platform Android or iOS. iOS, Android Android  
i Package Name (Android) or Bundle ID (iOS) of your application. iOS, Android com.yourcompany.app  
ip The IP of the event. iOS, Android 172.58.29.235  
idfa For iOS apps only. Upper-case raw advertising ID with dashes. iOS DFC5A647-9043-4699-B2A5-76F03A97064B  
idfv For iOS apps only. Upper-case raw IdentifierForVendor with dashes. iOS 21DB6612-09B3-4ECC-84AC-B353B0AF1334  
aifa For Android apps only. Lower-case raw advertising ID with dashes. Android 8ecd7512-2864-440c-93f3-a3cabe62525b  
andi For Android apps only. Lower-case raw android ID. Required only when Android Advertising ID is not available on the device. Android fc8d449516de0dfb  

Optional Event Parameters

Parameter Description Supported Platforms Example
utime Time of the event in UNIX time. Note: The time MUST NOT be less than the Install Time. iOS, Android 1483228800  
amt For revenue events. The currency amount expressed as a numerical value with decimal points (e.g. 2.51). This should be used in conjunction with the cur parameter. iOS, Android 2.51  
cur For revenue events. The ISO 4217 three-letter currency code. This should be used in conjunction with the amt parameter. iOS, Android EUR  
umilisec Time of the event in milliseconds UNIX time. Note: The time MUST NOT be less than the Install Time. iOS, Android 1483228800000  
e Event attributes formatted as a JSON encoded value. iOS, Android %7B%22item_name%22%3A
%20%22XBox%22%2C%0A
%22item_sku%22%3A%2011235813%7D
 
use_ip Extract the IP field from the HTTP request instead of the 'ip field. Don't use this with the ip parameter iOS, Android false  
purchase_receipt The receipt received from a purchase. See instructions below on how to retrieve it from Android, iOS iOS, Android

iOS:
MIISqwYJKoZI...cNqts0jvcNvPcK7y
uj0KhJ9nTTQ54kDKfReihzc6aw==

Android:
{"orderId":"GPA.1234",
"packageName":"com.example",
"productId":"com.example.product",
"purchaseTime":1417113074914,
"purchaseState":0,
"purchaseToken":"hakfcimbk... pM"} 

 
receipt_signature The signature used to sign the purchase receipt Android TyVJfHg8OAoW7W4wuJt... 5agEDMnNXvhfrw==
purchase_product_id The product SKU identifier iOS, Android

com.example.product

purchase_transaction_id The transaction identifier iOS, Android

iOS: 380000123004321

Android: GPA.1234-1234- 1234-12345 

is_revenue_event Whether it's a revenue event. You can omit this if the event name is __iap__ or a non-zero amt is provided. iOS, Android True
custom_user_id User ID  iOS, Android  123456789abcd

Event Tracking

Decide what events you want to track in your application. While there are a few restrictions on event names, you can generally use whichever you prefer. We highly recommend that events and their attributes be passed in English, to guarantee compatibility with many 3rd party partners and analytics solutions if you decide to use them when exporting data from Singular.

Event Length Limitations:

  • Event names: limited to 32 ASCII characters or in the case of non-ASCII characters, 32 bytes once converted to UTF-8.
  • Attributes and values: limited to 500 ASCII characters.

Sample Event Request

Python
import requests
import json

API_KEY = 'api_key_from_sdk_page'
EVENT_URL = 'https://s2s.singular.net/api/v1/evt'

params = {
 'n': 'levelup',
 'e': json.dumps({"level": "10"}),
 'a': API_KEY,
 'p': 'Android',
 'i': 'com.yourcompany.app',
 'ip': '10.1.2.3',
 'aifa': '8ecd7512-2864-440c-93f3-a3cabe62525b',
 'andi': 'fc8d449516de0dfb',
 'utime': 1483228800,
 'custom_user_id': '123456789abcd'
}

result = requests.get(EVENT_URL, params=params)
print result.json()
HTTP
https://s2s.singular.net/api/v1/evt?aifa=8ecd7512-2864-440c-93f3-a3cabe62525b&andi=fc8d449516de0dfb&p=Android&a=API_KEY&i=com.singular.app&n=levelup&e=%7B%22level%22%3A%20%2210%22%7D&ip=10.1.2.3&custom_user_id=123456789abcd
cURL
#GET
curl --request GET \
  --url 'https://s2s.singular.net/api/v1/evt?aifa=8ecd7512-2864-440c-93f3-a3cabe62525b&andi=fc8d449516de0dfb&p=Android&a=API_KEY&i=com.singular.app&n=levelup&e=%7B%22level%22%3A%20%2210%22%7D&ip=10.1.2.3&custom_user_id=123456789abcd'
Java
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.LinkedHashMap;
import java.util.Map;
import org.json.JSONObject; //From java-json.jar

public class SingularCustomEvent {
 public static void main(String[] args) {
  try {
   SingularCustomEvent.postEvent();
  } catch (Exception e) {
   e.printStackTrace();
  }
 }

 public static void postEvent() throws Exception {
  String singularAPIKey = "api_key_from_sdk_page";
  String singularEventEndpoint = "https://s2s.singular.net/api/v1/evt";

  Map < String, Object > params = new LinkedHashMap < > ();
  params.put("a", singularAPIKey);
  params.put("n", "sign_up");
  params.put("e", "{\"user_id\":\"1234\"}");
  params.put("p", "Android");
  params.put("i", "com.singular.app");
  params.put("ip", "10.1.2.3");
  params.put("aifa", "8ecd7512-2864-440c-93f3-a3cabe62525b");
  params.put("utime", 1483228800);
  params.put("andi", "fc8d449516de0dfb");
params.put("custom_user_id", "123456789abcd");

URL url = new URL(singularEventEndpoint); StringBuilder postData = new StringBuilder(); for (Map.Entry < String, Object > param: params.entrySet()) { if (postData.length() != 0) postData.append('&'); postData.append(URLEncoder.encode(param.getKey(), "UTF-8")); postData.append('='); postData.append(URLEncoder.encode(String.valueOf(param.getValue()), "UTF-8")); } byte[] postDataBytes = postData.toString().getBytes("UTF-8"); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod("POST"); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); conn.setRequestProperty("Content-Length", String.valueOf(postDataBytes.length)); conn.setDoOutput(true); conn.getOutputStream().write(postDataBytes); Reader in = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8")); StringBuilder sb = new StringBuilder(); for (int c; (c = in .read()) >= 0;) sb.append((char) c); // Print Singular JSON Response String response = sb.toString(); JSONObject singularResponse = new JSONObject(response.toString()); if (singularResponse.length() > 1) { System.out.println("status: " + singularResponse.getString("status")); System.out.println("reason: " + singularResponse.getString("reason")); // Handle Reason Code } else { System.out.println("status: " + singularResponse.getString("status")); } } }

Revenue Tracking

Along with tracking standard events, revenue events can also be tracked via S2S using an explicit revenue event. Events should be tracked as revenue to enable revenue and LTV data to appear in reporting.

Note: Revenue events allow for currency, amount, and optional product details to be available in exports and revenue postbacks.

Revenue Tracking Requirements

Currency code must be passed as the three-letter ISO 4217 currency code for correct tracking of revenue in the 'cur' parameter. E.g. 'cur=USD'
The amount must be passed in the 'amt' parameter. E.g. 'amt=1.99'
The event name passed can be used to breakout revenue type in dashboard reporting. E.g. 'n=RevenueName'.

Retrieving Purchase Receipt (Android)

Google provides an API to retrieve the purchase receipt and signature, known as the "In App Purchase Data" and "In App Data Signature", using the getBuyIntent() method.

Retrieving Purchase Receipt (iOS)

Apple's In-App Purchases API supports retrieving the purchase receipt using the implementation below. For more details, see Apple In-App Purchases API.

Objective-C
// SKPaymentTransactionObserver
    + (void)paymentQueue:(id)queue updatedTransactions:(NSArray *)skTransactions {
       NSString *transactionReceipt = nil;

       if ([skTransaction respondsToSelector:@selector(transactionReceipt)]) {
          NSData *transactionReceiptRaw = [skTransaction performSelector:@selector(transactionReceipt)];
          if (transactionReceiptRaw) {
             transactionReceipt = [ApUtils base64Encode:transactionReceiptRaw];
          }
       }
    }
Python
import requests

API_KEY = 'api_key_from_sdk_page'
EVENT_URL = 'https://s2s.singular.net/api/v1/evt'

params = {
 'n': 'Revenue Event',
 'a': API_KEY,
 'p': 'Android',
 'i': 'com.singular.app',
 'ip': '10.1.2.3',
 've': '9.2',
 'ma': 'samsung',
 'mo': 'SM-G935F',
 'lc': 'en_US',
 'aifa': '8ecd7512-2864-440c-93f3-a3cabe62525b',
 'utime': 1483228800,
 'amt': '2.5',
 'cur': 'EUR',
 'bd': 'Build/13D15'
}

result = requests.get(EVENT_URL, params=params)
print result.json()
HTTP
https://s2s.singular.net/api/v1/evt?n=__IAP__&a=API_KEY&p=Android&i=com.singular.app&ip=10.1.2.3&ve=9.2&ma=samsung&mo=SM-G935F&lc=en_US&aifa=8ecd7512-2864-440c-93f3-a3cabe62525b&andi=fc8d449516de0dfb&utime=1483228800&amt=2.50&cur=EUR&bd=Build%2F13D15
cURL
curl --request GET \
  --url 'https://s2s.singular.net/api/v1/evt?n=__IAP__&a=API_KEY&p=Android&i=com.singular.app&ip=10.1.2.3&ve=9.2&ma=samsung&mo=SM-G935F&lc=en_US&aifa=8ecd7512-2864-440c-93f3-a3cabe62525b&andi=fc8d449516de0dfb&utime=1483228800&amt=2.50&cur=EUR&bd=Build%2F13D15'

Additional Features

The Server-to-Server REST API also supports features that are available in the SDK as well. These special features are additions to the endpoint specifications and have additional requirements to use properly.

Google Play Referrer (Android)

Google Play Referrer is a parameter that can be passed into the Google Play Store, and then accessible within the app once it's installed.

Utilizing the Google Play Referrer helps us create the most accurate way to attribute Installs on Android, therefore it's important to collect and report this value to Singular.

Recommended Method of Collecting the Play Store Referrer from the App

The simplest and most recommended way of getting the install referrer is with the latest Google Play Referrer API and supports all apps installed with Play Store version 8.3.73 and later.

This method is the most secure way of getting the referrer value by directly communicating with the service.

Follow Google's documentation and implement the referrer fetching, then report the referrer values as described below.

Reporting the Play Store Referral to Singular

Use the Event Reporting Endpoint and use the reserved event name __InstallReferrer.

The __InstallReferrer Event must be sent immediately after every first session (app open) after install or re-install to allow the referrer data to be considered for Attribution.

Referrer Parameters

Parameter Description Example
referrer The value of the referrer received tracking_id%3D123456789&utm_ source%3Dmdotm%26utm_ edium%3Dbanner%26utm_ campaign%3Dcampaign
referrer_source The source of getting the referrer, specify "service" service
clickTimestampSeconds The click timestamp received only when the referrer source is service 1550420123
installBeginTimestampSeconds The time that the install started in. Only received when the intent source is service 1550420123
current_device_time The time on the current device in milliseconds 1550420454906

The JSON object has to be JSON encoded into a String, as with the example below:

Python
import requests
import json

API_KEY = 'api_key_from_sdk_page'
EVENT_URL = 'https://s2s.singular.net/api/v1/evt'

referrer_values = {
  "referrer": "tracking_id%3D123456789&utm_source%3Dmdotm%26utm_medium%3Dbanner%26utm_campaign%3Dcampaign",
  "referrer_source" : "service",
  "clickTimestampSeconds" : 1550420123,
  "installBeginTimestampSeconds" : 1550420123,
  "current_device_time" : 1550420454906
}

params = {
    'n': '__InstallReferrer',
    'e': json.dumps(referrer_values),
    'a': API_KEY,
    'p': 'Android',
    'i': 'com.singular.app',
    'ip': '10.1.2.3',
    've': '9.2',
    'ma': 'samsung',
    'mo': 'SM-G935F',
    'lc': 'en_US',
    'aifa': '8ecd7512-2864-440c-93f3-a3cabe62525b',
    'andi': 'fc8d449516de0dfb',
    'utime': 1483228800,
    'bd': 'Build/13D15'
}

result = requests.get(EVENT_URL, params=params)
print result.json()
HTTP
https://s2s.singular.net/api/v1/evt?mo=SM-A505F&bd=Build%2F13D15&utime=1568944567&ip=10.1.2.3&i=com.singular.app&n=__InstallReferrer&e=%7B%22installBeginTimestampSeconds%22%3A%221568939453%22%2C%22referrer%22%3A%22utm_source%3Dgoogle-play%26utm_medium%3Dorganic%22%2C%22clickTimestampSeconds%22%3A%220%22%2C%22referrer_source%22%3A%22service%22%2C%22current_device_time%22%3A%221568944524%22%7D%0A&aifa=8ecd7512-2864-440c-93f3-a3cabe62525b&ve=9.2&p=Android&ma=samsung&mo=SM-G935F&lc=en_US&a=API_KEY

Apple Search Ads (iOS)

Collecting Apple Search Ads Attribution Data from the App

Apple Search Ads is a Self-Attributing Network (SAN), which requires collecting the attribution data from the iOS App itself.

Below is an example code snippet on how to collect Apple Search Ads attribution data:

Objective-C
#import <iAd/iAd.h>

  Class ADClientClass = NSClassFromString(@"ADClient");

  if (ADClientClass) {
      id sharedClient = [ADClientClass performSelector:@selector(sharedClient)];
      
      if ([sharedClient respondsToSelector:@selector(requestAttributionDetailsWithBlock:)]) {
          [sharedClient requestAttributionDetailsWithBlock:^(NSDictionary *attributionDetails, NSError *error) {
              if (attributionDetails && attributionDetails.count > 0) {
                  // REPORT attributionDetails FROM YOUR APP TO YOUR SERVER
              }
          }];
      }
  }

For more information see Apple Search Ads Attribution API.

Search Ads Attribution API - Bulletin:

Most users that click on a Search Ads impression immediately download the app. When the app is opened immediately and the Search Ads Attribution API is called, the corresponding ad click might not have been processed by our system yet due to some latency. Apple recommends:

  1. Setting a delay of a few seconds before retrieving attribution data.
  2. Implementing retry logic if response returns as False or Error Code [0,2,3].
  3. Calling the Apple Attribution API again 2 seconds later.

Reporting Apple Search Ads Attribution Data to Singular

Use the Event Reporting Endpoint and use the reserved event name __iAd_Attribution__.
Pass the apple_attribution_data as a JSON encoded value, for example:

The __iAd_Attribution__ Event must be sent immediately after the first session (app open) after install or re-install to allow the ASA data to be considered for Attribution. 

Python
import requests
import json

API_KEY = 'api_key_from_sdk_page'
EVENT_URL = 'https://s2s.singular.net/api/v1/evt'

# !!! REPLACE WITH COLLECTED VALUE FROM APP !!!
apple_attribution_data = {
    u'Version3.1': {
        u'iad-adgroup-id': u'1234567',
        u'iad-adgroup-name': u'Ad Group Name',
        u'iad-attribution': u'true',
        u'iad-campaign-id': u'1234567',
        u'iad-campaign-name': u'Search Campaign',
        u'iad-click-date': u'2016-05-21T12:19:31Z',
        u'iad-conversion-date': u'2016-05-21T12:19:41Z',
        u'iad-keyword': u'ballon',
        u'iad-lineitem-id': u'1234567',
        u'iad-lineitem-name': u'Line Item Name',
        u'iad-org-name': u'Cool Company',
        u'iad-purchase-date': u'2016-05-21T12:19:41Z'
    }
}

params = {
    'n': '__iAd_Attribution__',
    'e': json.dumps(apple_attribution_data),
    'a': API_KEY,
    'p': 'iOS',
    'i': 'com.singular.app',
    'ip': '10.1.2.3',
    've': '9.2',
    'mo': 'iPhone9%2C4',
    'lc': 'en_US',
    'idfa': '8ECD7512-2864-440C-93F3-A3CABE62525B',
    'idfv': '38548D9F-3F73-4D4B-8545-9A920CC89191',
    'utime': 1483228800
}

result = requests.get(EVENT_URL, params=params)
print result.json()
HTTP
https://s2s.singular.net/api/v1/evt?n=__iAd_Attribution__&e=%7B%22Version3.1%22%3A%7B%22iad-purchase-date%22%3A%20%222016-10-25T22%3A24%3A35Z%22%2C%22iad-keyword%22%3A%20%22ballon%22%2C%22iad-adgroup-id%22%3A%20%221234567%22%2C%22iad-campaign-id%22%3A%20%221234567%22%2C%22iad-lineitem-id%22%3A%20%221234567%22%2C%22iad-org-id%22%3A%20%224070%22%2C%22iad-org-name%22%3A%20%22Cool%20Company%22%2C%22iad-campaign-name%22%3A%20%22Search%20Campaign%22%2C%22iad-conversion-date%22%3A%20%222016-05-21T12%3A19%3A41Z%22%2C%22iad-conversion-type%22%3A%20%22Redownload%22%2C%22iad-click-date%22%3A%20%222016-05-21T12%3A19%3A31Z%22%2C%22iad-attribution%22%3A%20%22true%22%2C%22iad-adgroup-name%22%3A%20%22Ad%20Group%20Name%22%2C%22iad-lineitem-name%22%3A%20%22Line%20Item%20Name%22%7D%7D&a=API_KEY&p=iOS&i=com.singular.app&ip=10.1.2.3&ve=9.2&ma=Apple&mo=iPhone8%2C1&lc=en_US&idfa=8ECD7512-2864-440C-93F3-A3CABE62525B&idfv=38548D9F-3F73-4D4B-8545-9A920CC89191&utime=1568948680

iOS Install Receipt

Tto retrieve the install receipt, paste the following code into your app:

Objective-C
+ (NSString*)installReceipt {
      // install receipts are iOS 7.0+
      if (NSFoundationVersionNumber < NSFoundationVersionNumber_iOS_7_0) {
          return nil;
      }
      
      NSURL *receiptURL = [[NSBundle mainBundle] appStoreReceiptURL];
      
      if (receiptURL) {
          NSData *receipt = [NSData dataWithContentsOfURL:receiptURL];
          
          if (receipt) {
              return [receipt base64EncodedStringWithOptions:0];
          }
      }
      
      return nil;
  }

Custom User ID

The Singular platform supports ingesting a custom user ID which is returned in user-level exports and optionally, in the Internal BI postback.

Pass the Custom User ID on the 'Session' or the launch call. (This method should ONLY be used if the Custom User ID is available at the time of App Open.) Update the Session Request to include the extra parameter 'custom_user_id' and assign the value. See sample below:

Python
import requests
import json

API_KEY = 'api_key_from_sdk_page'
LAUNCH_URL = 'https://s2s.singular.net/api/v1/launch'

params = {
    'a': API_KEY,
    'p': 'Android',
    'i': 'com.singular.app',
    'ip': '10.1.2.3',
    've': '9.2',
    'ma': 'samsung',
    'mo': 'SM-G935F',
    'lc': 'en_US',
    'aifa': '8ecd7512-2864-440c-93f3-a3cabe62525b',
    'andi': 'fc8d449516de0dfb',
    'utime': 1483228800,
    'dnt': 0,
    'install':'true',
    'n': 'MyCoolApp',
    'c': 'wifi',
    'cn': 'Comcast',
    'bd': 'Build/13D15',
    'fcm':'bk3RNwTe3H0CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1',
    'app_v':'1.2.3',
    'openuri':'myapp%3A%2F%2Fhome%2Fpage%3Fqueryparam1%3Dvalue1',
    'ddl_enabled':'false',
    'install_source': 'com.android.vending',
    'install_time': 1510040127,
    'update_time': 1510090877,
    'custom_user_id': 'player_id_1234'
}

result = requests.get(LAUNCH_URL, params=params)
print result.json()
HTTP
https://s2s.singular.net/api/v1/launch?aifa=8ecd7512-2864-440c-93f3-a3cabe62525b&andi=fc8d449516de0dfb&p=Android&a=API_KEY&i=com.singular.app&ip=10.1.2.3&ve=9.2&n=MyCoolApp&dnt=0&c=wifi&cn=Comcast&lc=en_US&bd=Build%2FMMB29K&ma=samsung&mo=SM-G935F&custom_user_id=player_id_1234
cURL
curl --request GET \
  --url 'https://s2s.singular.net/api/v1/launch?aifa=8ecd7512-2864-440c-93f3-a3cabe62525b&andi=fc8d449516de0dfb&p=Android&a=API_KEY&i=com.singular.app&ip=10.1.2.3&ve=9.2&n=MyCoolApp&dnt=0&c=wifi&cn=Comcast&lc=en_US&bd=Build%2FMMB29K&ma=samsung&mo=SM-G935F&custom_user_id=player_id_1234'

Uninstall Tracking

Uninstall tracking is done using Silent Push Notifications.

To enable it you'll need to send the device push token to Singular servers. The token should be sent in the session (api/v1/launch?) when available.

Android Uninstall Tracking

In order to enable Android uninstall tracking, the FCM device token must be retrieved from the app first. When you need to retrieve the current token, call FirebaseInstanceId.getInstance().getToken().

This method returns null if the token has not yet been generated.

For more information, see the Google Firebase Documentation.

After the FCM device token is retrieved, you will need to pass the device token when reporting the session to Singular, like:

Python
import requests
import json

API_KEY = 'api_key_from_sdk_page'
LAUNCH_URL = 'https://s2s.singular.net/api/v1/launch'

params = {
    'a': API_KEY,
    'p': 'Android',
    'i': 'com.singular.app',
    'ip': '10.1.2.3',
    've': '9.2',
    'ma': 'samsung',
    'mo': 'SM-G935F',
    'lc': 'en_US',
    'aifa': '8ecd7512-2864-440c-93f3-a3cabe62525b',
    'andi': 'fc8d449516de0dfb',
    'utime': 1483228800,
    'dnt': 0,
    'n': 'MyCoolApp',
    'c': 'wifi',
    'cn': 'Comcast',
    'bd': 'Build/13D15',
    'fcm': 'bk3RNwTe3H0CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1'
}

result = requests.get(LAUNCH_URL, params=params)
print result.json()
HTTP
https://s2s.singular.net/api/v1/launch?a=API_KEY&p=Android&i=com.singular.app&ip=10.1.2.3&ve=9.2&ma=samsung&mo=SM-G935F&lc=en_US&aifa=8ecd7512-2864-440c-93f3-a3cabe62525b&andi=fc8d449516de0dfb&utime=1483228800&dnt=0&n=MyCoolApp&c=wifi&cn=Comcast&bd=Build%2F13D15&fcm=bk3RNwTe3H0CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1
cURL
curl --request GET \
  --url 'https://s2s.singular.net/api/v1/launch?a=API_KEY&p=Android&i=com.singular.app&ip=10.1.2.3&ve=9.2&ma=samsung&mo=SM-G935F&lc=en_US&aifa=8ecd7512-2864-440c-93f3-a3cabe62525b&andi=fc8d449516de0dfb&utime=1483228800&dnt=0&n=MyCoolApp&c=wifi&cn=Comcast&bd=Build%2F13D15&fcm=bk3RNwTe3H0CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1'

iOS Uninstall Tracking

Uninstall tracking on iOS utilizes Apple push notification technology. If your app currently doesn't support push notifications, see Apple's guide.

If your app already supports Apple push notifications, all you need to do is pass the device token returned from APNS before reporting the session to Singular.

The APNS token is usually binary data in the native form (ie. on iOS), pass the token as received from APNS. If the token data type is being altered by App code, be sure it is passed as a hex-encoded string. The format should look similar to the below sample.

b0adf7c9730763f88e1a048e28c68a9f806ed032fb522debff5bfba010a9b052

Our assumption is that you are already retrieving a device token from an existing push notification implementation. If so - you can simply use that value. Pass the device token in the session like:

Python
import requests
import json

API_KEY = 'api_key_from_sdk_page'
LAUNCH_URL = 'https://s2s.singular.net/api/v1/launch'

params = {
    'a': API_KEY,
    'p': 'iOS',
    'i': '162738612',
    'ip': '10.1.2.3',
    've': '9.2',
    'mo': 'iPhone9%2C4',
    'lc': 'en_US',
    'idfa': '8ECD7512-2864-440C-93F3-A3CABE62525B',
    'idfv': '38548D9F-3F73-4D4B-8545-9A920CC89191',
    'utime': 1483228800,
    'dnt': 0,
    'n': 'MyCoolApp',
    'c': 'wifi',
    'cn': 'Comcast',
    'bd': 'Build/13D15',
    'apns_token': 'b0adf7c9730763f88e1a048e28c68a9f806ed032fb522debff5bfba010a9b052'
}

result = requests.get(LAUNCH_URL, params=params)
print result.json()
HTTP
https://s2s.singular.net/api/v1/launch?a=API_KEY&p=iOS&i=162738612&ip=10.1.2.3&ve=9.2&mo=iPhone9%2C4&lc=en_US&idfa=8ECD7512-2864-440C-93F3-A3CABE62525B&idfv=38548D9F-3F73-4D4B-8545-9A920CC89191&utime=1483228800&dnt=0&n=MyCoolApp&c=wifi&cn=Comcast&bd=Build%2F13D15&apns_token=b0adf7c9730763f88e1a048e28c68a9f806ed032fb522debff5bfba010a9b052
cURL
curl --request GET \
  --url 'https://s2s.singular.net/api/v1/launch?a=API_KEY&p=iOS&i=162738612&ip=10.1.2.3&ve=9.2&mo=iPhone9%2C4&lc=en_US&idfa=8ECD7512-2864-440C-93F3-A3CABE62525B&idfv=38548D9F-3F73-4D4B-8545-9A920CC89191&utime=1483228800&dnt=0&n=MyCoolApp&c=wifi&cn=Comcast&bd=Build%2F13D15&apns_token=b0adf7c9730763f88e1a048e28c68a9f806ed032fb522debff5bfba010a9b052'

Singular Links

Singular Links support for S2S integrations require:

iOS Support

Android Support

Update the LAUNCH API Request

Singular Links requires Singular to receive EVERY App Open via the LAUNCH API Request. If the app opened because of a deep link / Universal Link, pass the encoded open URL in openuri= parameter. If the app opened for the first time since download/install, enable deferred deep link flow by sending install=true and ddl_enabled=true.a

Python
import requests
    import json

    API_KEY = 'api_key_from_sdk_page'
    LAUNCH_URL = 'https://s2s.singular.net/api/v1/launch'

    params = {
        'a': API_KEY,
        'p': 'iOS',
        'i': '162738612',
        'ip': '10.1.2.3',
        've': '9.2',
        'mo': 'iPhone9%2C4',
        'lc': 'en_US',
        'idfa': '8ECD7512-2864-440C-93F3-A3CABE62525B',
        'idfv': '38548D9F-3F73-4D4B-8545-9A920CC89191',
        'utime': 1483228800,
        'dnt': 0,
        'n': 'MyCoolApp',
        'c': 'wifi',
        'cn': 'Comcast',
        'bd': 'Build/13D15',
        'openuri':'https://myapp.sng.link/A59c0/nha7?_dl=myapp%3A%2F%2Fdeeplink&_ddl=myapp%3A%2F%2Fdeferred-deeplink&_p=passthroughvalue',
        'install':'true',
        'ddl_enabled':'true'
    }

    result = requests.get(LAUNCH_URL, params=params)
    print result.json()

For a tracking link which was enabled with deferred deep linking, the Launch endpoint will respond with JSON containing the deferred deep link value and deferred passthrough value which can be used in the App:

JSON Response
{
  "deferred_deeplink":"myapp://deferred-deeplink",
  "status":"ok",
  "deferred_passthrough":"passthroughvalue"
}

The app must also handle the following values, which will be present on the open URL in the event of a successful deep link caused by a Singular Link:

_dl
*_ios_dl
*_android_dl
_p

Note: *_ios_dl and *_android_dl will be present on a link when the link was generated to deep link on both iOS/Android AND deep link values are different between iOS and Android. Otherwise, if deep link values are the same for iOS/Android, only _dl will be present.

For more information on Passing Dynamic Data to the App with Passthroughs, see Singular Links Best Practices and Examples.

Deferred Deep Linking (Legacy)

Singular servers have the ability to report a deferred deep link value/URL back to your servers. Your servers can then utilize this value and serve this value back to the device to create a deferred deep link experience for your users.

Deferred deep links are defined when tracking links are generated. It is recommended that there is coordination between your development team and the user responsible for generating tracking links, to ensure expected deferred deep link values are returned.

Deferred deep link values can be returned in the response to a launch GET request. Note that the ddl_enabled value must be set to true in order for Singular servers to look-up and retrieve a potential deferred deep link value.

Python
import requests
    import json

    API_KEY = 'api_key_from_sdk_page'
    LAUNCH_URL = 'https://s2s.singular.net/api/v1/launch'

    params = {
        'a': API_KEY,
        'p': 'Android',
        'i': 'com.singular.app',
        'ip': '10.1.2.3',
        've': '9.2',
        'ma': 'samsung',
        'mo': 'SM-G935F',
        'lc': 'en_US',
        'aifa': '8ecd7512-2864-440c-93f3-a3cabe62525b',
        'andi': 'fc8d449516de0dfb',
        'utime': 1483228800,
        'dnt': 0,
        'install':'true',
        'n': 'MyCoolApp',
        'c': 'wifi',
        'cn': 'Comcast',
        'bd': 'Build/13D15',
        'fcm':'bk3RNwTe3H0CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1',
        'app_v':'1.2.3',
        'ddl_enabled':'true',
        'install_source': 'com.android.vending',
        'install_time': 1510040127,
        'update_time': 1510090877
    }

    result = requests.get(LAUNCH_URL, params=params)
    result_json = result.json()
    if result_json["status"] == "ok":
        print "deferred_deeplink = %s" % result_json["deferred_deeplink"]
    else:
        print "failure"

Reference: Retrieving Device Identifiers and Session Data

This section details how you can retrieve certain values required in the REST API. These values are industry standard, and Apple and Google have documentation on them as well. We have provided a few reference implementation here for your convenience.

Retrieving Identifiers (Android)

Google Advertising ID/Limit Ad Tracking

Google Play Services SDK is required to obtain the Advertising ID in your app. Add the following tag as a child of your application element in AndroidManifest.xml:

XML
<meta-data android:name="com.google.android.gms.version"
           android:value="@integer/google_play_services_version" />

Sample code for getting the Google Advertising ID/Limit Ad Tracking parameters:

Java
import com.google.android.gms.ads.identifier.AdvertisingIdClient;
    import com.google.android.gms.ads.identifier.AdvertisingIdClient.Info;
    import com.google.android.gms.common.GooglePlayServicesAvailabilityException;
    import com.google.android.gms.common.GooglePlayServicesNotAvailableException;
    import java.io.IOException;
      
    // Do not call this function from the main thread. Otherwise, 
    // an IllegalStateException will be thrown.
    public void getIdAndLAT() {
        Info adInfo = null;
        try {
            adInfo = AdvertisingIdClient.getAdvertisingIdInfo(mContext);
        } catch (IOException e) {
            // Unrecoverable error connecting to Google Play services (e.g.,
            // the old version of the service doesn't support getting AdvertisingId).
        } catch (GooglePlayServicesAvailabilityException e) {
            // Encountered a recoverable error connecting to Google Play services. 
        } catch (GooglePlayServicesNotAvailableException e) {
            // Google Play services is not available entirely.
        }
        final String GAID = adInfo.getId();
        final boolean limitAdTracking = adInfo.isLimitAdTrackingEnabled();
    }

Locale, Device, and Build for Android

Java
// Locale - lc= query parameter
    String locale = Locale.getDefault();

    // Model - mo= query parameter
    String device = Build.MODEL;
      
    // Build - bd= query parameter
    String build = "Build/" + Build.ID;

Retrieving Identifiers (iOS)

Apple Advertising ID (IDFA) / Limit Ad Tracking

Import the AdSupport class at the top of your file:

Objective-C
@import AdSupport;
Swift
@import AdSupport;

Sample code for getting the IDFA/Limit Ad Tracking parameters:

Objective-C
NSString* IDFA = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];
NSString* IDFV = [[[UIDevice currentDevice] identifierForVendor] UUIDString];

BOOL isAdvertisingTrackingEnabled = [[ASIdentifierManager sharedManager] isAdvertisingTrackingEnabled];
Swift
Class AdSupportClass = NSClassFromString(@"ASIdentifierManager");

id sharedManager = nil;

if (AdSupportClass && [AdSupportClass respondsToSelector:@selector(sharedManager)]) {
  sharedManager = [AdSupportClass performSelector:@selector(sharedManager)];
}

if (sharedManager) {
    NSString* IDFA = [[[ASIdentifierManager sharedManager] advertisingIdentifier] UUIDString];
    NSString* IDFV = [[[UIDevice currentDevice] identifierForVendor] UUIDString];
    BOOL dnt = ![[ASIdentifierManager sharedManager] isAdvertisingTrackingEnabled];

The dnt (Do Not Track) parameter should be passed in Session requests.

Locale, Device, and Build for iOS

Objective-C
// Locale - lc= query parameter
NSString *locale = [[NSLocale currentLocale] localeIdentifier];

// Model - mo= query paramter
#import <sys/sysctl.h>
NSString *device() {
    size_t bufferSize = 64;
    NSMutableData * buffer = [[NSMutableData alloc] initWithLength:bufferSize];
    int status = sysctlbyname("hw.machine", buffer.mutableBytes, &bufferSize, NULL, 0);
    if (status != 0) {
        return nil; 
    }
    return [[NSString alloc] initWithData:buffer encoding:NSUTF8StringEncoding];
}

// Build - bd= query parameter
NSString * build () { 
    size_t bufferSize = 64;
    NSMutableData *buffer = [[NSMutableData alloc] initWithLength:bufferSize];
    int status = sysctlbyname("kern.osversion",buffer.mutableBytes, &bufferSize, NULL, 0);
    if (status != 0) {
        return nil;
    }
    return [[NSString alloc] initWithData:buffer encoding:NSUTF8StringEncoding];
}
Swift
// Locale - lc= query parameter
NSString *locale = [[NSLocale currentLocale] localeIdentifier];
  
// Model - mo= query paramter
@import Darwin.sys.sysctl;
NSString *device(void) {
    size_t bufferSize = 64;
    NSMutableData * buffer = [[NSMutableData alloc] initWithLength:bufferSize];
    int status = sysctlbyname("hw.machine", buffer.mutableBytes, &bufferSize, NULL, 0);
    if (status != 0) {
        return nil; 
    }
    return [[NSString alloc] initWithData:buffer encoding:NSUTF8StringEncoding];
}

// Build - bd= query parameter
@import Darwin.sys.sysctl;
NSString * build ( void ) { 
    size_t bufferSize = 64;
    NSMutableData *buffer = [[NSMutableData alloc] initWithLength:bufferSize];
    int status = sysctlbyname("kern.osversion",buffer.mutableBytes, &bufferSize, NULL, 0);
    if (status != 0) {
        return nil;
    }
    return [[NSString alloc] initWithData:buffer encoding:NSUTF8StringEncoding];
}
Was this article helpful?