NinthDecimal Server-to-Server Integration

Overview

The NinthDecimal Server-to-Server integration allows thirty parties to integrate with the NinthDecimal platform by making server-to-server calls instead of using a NinthDecimal Mobile or Web SDK.

The following flow is used when initiating server-to-server integration:

  1. Initiate a user session when a device comes online by calling the Session endpoint.
  2. For every action your application deems rewardable, make a server call using the Moment endpoint. The NinthDecimal Platform will return a reward if we are able to reward the moment. When testing, passing the test flag as true will ensure a reward is always returned.

Note: Due to GDPR regulations, NinthDecimal is now blocking all ad requests from the affected EEA regions.

When testing from outside the US it is recommended to simulate a US-based location when requesting rewards. Test rewards fill 100% of the time, whereas with live/production rewards it is expected that not every request will be filled with a reward. It is important that you add the correct lat/long coordinates inside the location block of the API request.

Session Endpoint

Start a session when a user opens the app. This is required so NinthDecimal can receive moment/reward requests while the app is in use. The below describes the format of the call needed to start a session.

POST /2.0/server/moment/
Accept: application/json
Content-Type: application/json
Host: api.kiip.me

{
	"app”:{
		"app_key":<"app key found in NinthDecimal dashboard">,
		"version":<"client version number of the app">,
		"dns_compliance":<"boolean on app publisher is CCPA compliant">
	},
	"connection":{
		"ip":<"ip address of the device">,
		"carrier":<"e.g. 'ATT'">,
		"type":<"Connection type e.g. 'Wifi'">
	},
	"device":{
		"id":<"The Advertising Identifier provided by the device">,
		"density":<"screen density e.g. '3.5'">,
		"lang":<"e.g. 'en'">,
		"locale":<"e.g. en_US">,
		"manufacturer":<"device manufacturor name e.g. 'Apple'">,
		"model":<"e.g. 'nexus_5'">,
		"os":<"e.g. Android 5.0">,
		"resolution":<"e.g. 1080x1776">
		"user_agent":<"e.g. Mozilla/5.0 (iPhone; CPU iPhone OS 11_2_6 like Mac OS X) AppleWebKit/604.5.6 (KHTML, like Gecko) Mobile/15D100">
	},
	"location":{
		"lat":<"float e.g. '39.04371920'">,
		"lng":<"float e.g. '77.48748990'">,
		"accuracy": <"int e.g 10">,
	},
	"bundle_id":<"Apple App Store or Google Play bundle ID">,
	"close_button":<"Can be used to hide the close reward button when set to 0 or false">,
	"email_address":<"Used to prefill the email address for earned rewards">,
	"test":<"set to either true or false">,
	"sdk_version":<"NinthDecimal SDK version">,
	"user":{
		"age":<"years old">,
		"gender":<"either 'm' or 'f' if available">,
		"userid":<"string">

	},
	"events:[
		{
		"id":<"session_start">,
		"start":<"ISO 8601 date format">
		}
	],
	"user_consent_info": {
		"user_compliance": <"A boolean if user has opted out of selling data. True if the user has opted out and false otherwise">,
		"state": <"Two character home state code (e.g. CA for California) of the user (if known)">
	}
}

JSON Payload Components

Below is a list of all required payload components. If you have any questions about the use of these components, please contact support@kiip.me.

device

  • id: The ADID or Identifier
  • density: The screen density (if available)
  • locale: The user’s setting for locale
  • os: This should always include a version number
  • lang: Set at the device level
  • model: Set at the device level
  • resolution: Set at the device level
  • manufacturer: The device’s manufacturer
  • user_agent: User agent of the device’s browser

app

  • app_key: NinthDecimal application key found in the dashboard
  • version: The current client version number
  • dns_compliance: The status of your CCPA compliance. True if compliant and false otherwise.

connection

  • ip: a valid ip address provided by device
  • carrier: Provided by device e.g. ‘ATT’
  • type: Provided by device e.g. ‘WIFI’

test

In order to test your NinthDecimal implementation, this should be set to true. In a live environment, this must be set to false.

sdk_version

The current Server-to-Server SDK is version 1.0.

user

  • age: User age (if available)
  • gender: Users gender, either ‘m’ or ‘f’ (if available)
  • userid: A string containing a user’s id for use in virtual currency rewards

location

  • lat: This is always a number between -90 and 90
  • lng: This is always a number between -180 and 180
  • accuracy: This is an integer that is always > 0

Note: To send horizontal accuracy (recommended), include the “accuracy” attribute.

bundle_id

This is the app’s Apple App Store or Google Play bundle ID if it exists.

close_button

By default, a close button is shown when rewards are earned. Can be used to hide the close reward button when set to 0 or false.

email_address

When supplied with an email address, this component will be used to prefill a user’s email address when a reward is earned.

events

  • id: Should be either ‘session_start’ or ‘session_end’ depending whether the user opens or closes the app
  • start: ISO 8601 date format
  • user_compliance: As part of CCPA compliance, this is the status of whether the user has opted out of selling data. True means the user has opted out and false means the user has not opted out.
  • state: As part of CCPA compliance, this lets us know the home state code (e.g. CA for California) of the user (if known)

Example Request

https://api.kiip.me/2.0/server/moment

Payload
{
	"app”:{
		"app_key":"a05a2fd245170d452de01a6abc786922",
		"version":"4281020",
		"dns_compliance":true,
	},
	"connection":{
		"ip":"54.174.101.1",
		"type":"WIFI"
	},
	"device":{
		"id":"45e3-1d799e20-963a-4ddd-8593-0683b30e7d5b",
		"lang":"en",
		"locale":"en_US",
		"manufacturer":"google",
		"model":"nexus_5",
		"os":"Android 5.0",
		"resolution":"1080x1776"
	},
	"bundle_id":"me.kiip.SampleApp",
	"location":{
		"lat":39.04371920,
		"lng":77.48748990,
		"accuracy": 10,
	},
	"sdk_version":"1.0",
	"test":true,
	"user":{
		"gender":"m"
	},
	"events":[
		{
			"id":"session_start",
			"start":"2015-01-28T01:32:22.852Z"
		}
	],
	"user_consent_info": {
		"user_compliance":false,
		"state":"CA"
	}
}

Response

The server will respond to valid requests with a JSON document containing a single key date holding the session start time in ISO8601 format.

{
     "date": "2015-01-28T01:32:24.697136"
}

Moment Endpoint

When a user completes a rewardable event, the server should make a call to the moment endpoint requesting a reward. The call is described below. If NinthDecimal deems the moment rewardable, a JSON document will be returned containing the reward information. Otherwise, no reward will be returned.

POST /2.0/server/moment/
Accept: application/json
Content-Type: application/json
Host: api.kiip.me

{
	"app”:{
		"app_key":<"app key found in NinthDecimal dashboard">,
		"version":<"client version number of the app">,
		"dns_compliance":<"boolean on app publisher is CCPA">
	},
	"connection":{
		"ip":<"ip address of the device">,
		"carrier":<"e.g. 'ATT'">,
		"type":<"Connection type e.g. 'Wifi'">
	},
	"device":{
		"id":<"The Advertising Identifier provided by the device">,
		"density":<"screen density e.g. '3.5'">,
		"lang":<"e.g. 'en'">,
		"locale":<"e.g. en_US">,
		"manufacturer":<"device manufacturor name e.g. 'Apple'">,
		"model":<"e.g. 'nexus_5'">,
		"os":<"e.g. Android 5.0">,
		"resolution":<"e.g. 1080x1776">
	},
	"location":{
		"lat":<"float e.g. '39.04371920'">,
		"lng":<"float e.g. '77.48748990'">,
		"accuracy": <"int e.g 10">,
	},
	"moment":{
		"id":<"UUID found in the NinthDecimal dashboard associated with a given moment">
	},
	"bundle_id":<"Apple App Store or Google Play bundle ID">,
	"close_button":<"Can be used to hide the close reward button when set to '0' or 'false'">,
	"email_address":<"Used to prefill the email address for earned rewards">,
	"sdk_version":<"NinthDecimal SDK version">,
	"test":<"either true or false">,
	"user":{
		"age":<"years old">,
		"gender":<"either 'm' or 'f' if available">,
		"userid":<"string">
	},
	"user_consent_info": {
		"user_compliance": <"A boolean if user has opted out of selling data. True if the user has opted out and false otherwise">,
		"state": <"Two character home state code (e.g. CA for California) of the user (if known)">
	}
}

JSON Payload Components

Below is a list of all required payload components for a Moment Endpoint. If you have any questions about the use of these components, please contact support@kiip.me

device

  • id: The ADID or Identifier
  • density: The screen density (if available)
  • locale: The user’s setting for locale
  • os: This should always include a version number
  • lang: Set at the device level
  • model: Set at the device level
  • resolution: Set at the device level
  • manufacturer: The device’s manufacturer

app

  • app_key: NinthDecimal application key found in the dashboard
  • version: The current client version number
  • dns_compliance: The status of your CCPA compliance. True if compliant and false otherwise.

moment

  • id: The uuid associated with the a given moment from the NinthDecimal dashboard

bundle_id

This is the app’s Apple App Store or Google Play bundle ID if it exists.

close_button

Unless explicitly set to 0 or false the reward close button will be present.

email_address

When supplied with an email address, this component will be used to prefill a user’s email address when a reward is earned.

connection

  • ip: A valid ip address, provided by device
  • carrier: Provided by device e.g. ‘ATT’
  • type: Provided by device e.g. ‘WIFI’

test

In order to test your NinthDecimal implementation, this should be set to true. In a live environment, this must be set to false.

sdk_version

Currently the Server-to-Server sdk is in version 1.0.

user

  • age: User age (if available)
  • gender: Users gender, either ‘m’ or ‘f’ (if available)
  • userid: A string containing a user’s id for use in virtual currency rewards

location

  • lat: This is always a number between -180 and 180
  • lng: This is always a number between -180 and 180
  • accuracy: This is an integer that is always > 0
  • user_compliance: As part of CCPA compliance, this is the status of whether the user has opted out of selling data. True means the user has opted out and false means the user not has opted out.
  • state: As part of CCPA compliance, this lets us know the home state code (e.g. CA for California) of the user (if known)

Note: To send horizontal accuracy (recommended), include the “accuracy” attribute.

Example Request

https://api.kiip.me/2.0/server/moment

Payload
{
	"app”:{
		"app_key":"a05a2fd245170d452de01a6abc786922",
		"version":"4281020"
		"dns_compliance":true,
	},
	"connection":{
		"ip":"54.174.101.1",
		"type":"WIFI"
	},
	"device":{
		"id":"45e3-1d799e20-963a-4ddd-8593-0683b30e7d5b",
		"lang":"en",
		"locale":"en_US",
		"manufacturer":"google",
		"model":"nexus_5",
		"os":"Android 5.0",
		"resolution":"1080x1776"
	},
	"location":{
		"lat":39.04371920,
		"lng":77.48748990,
		"accuracy": 10,
	},
	"moment":{
		"id":"CgkIraKVsdIBEAIQEA"
	},
	"bundle_id":"me.kiip.SampleApp",
	"close_button":"",
	"email_address":"hello@kiip.me",
	"sdk_version":"2.3.0",
	"test":false,
	"user":{
		"gender":"f"
	},
	"events":[
		{
			"id":"session_start",
			"start":"2015-01-28T01:32:22.852Z"
		}
	],
	"user_consent_info": {
		"user_compliance":true,
		"state":"WA"
	}
}

Example Response

Note: The ‘notification’ blob in the response contains the reward name and the reward creative URL which can be used in the reward notifications in the app. Reward notifications are optional but this blob will be required if using reward notifications.

{
	"date": "2016-07-07T18:47:06.353111", 
	"notification": {
		"reward_name": "Test Reward - Virtual Click", 
		"reward_image_url": "https://d3aq14vri881or.cloudfront.net/5473ca60-2891-a32d-d30d-cd6f3d4dcc67_KiipRewardTemplate.png"
	}, 
	"view": {
		"email_bounced": false,
		"id": "test_5473ca54-59a5-cf65-d319-c91697297fcc", 
		"modal": {
			"message": "Test Reward - Virtual Click", 
			"timeout": 10, 
			"body_url": "https://api.kiip.me/reward/577ea3aa-b723-ae81-660c-79fae3443e12/5473ca54-59a5-cf65-d319-c91697297fcc?application_id=565a559f-a0ef-3384-66bc-669e61d12c93&locale=en_US&moment_id=575130b8-2637-d8f4-e587-cce64f693f4a&sdk_version=1.0&r=153891&mode=standalone&preview=true&sdk=none",
 			"title": "Loading your NinthDecimal Reward:"
		}, 
	"used_date": null
	}
}

Verify a Reward Redemption

If you choose to validate a reward redemption, you may via a server callback. First you will have to set up a callback URL. This requires creating an endpoint on an accessible server.

Currently, the reward redemption callback URL may only be used with virtual currency reward redemptions.

Required: Callback URLs must be served over HTTPS

Issue Virtual Currency

When a user redeems a reward containing virtual currency, the NinthDecimal servers will immediately POST a request to the callback URL specified in your setup. This postback does require a user_id be set on the NinthDecimal object.

This requires creating an endpoint on an accessible server.

The POST request will contain the following data:

{
	   "transaction_id": <reward id>,
	   "content": <virtual_uuid>,
	   "name": <currency name>,
	   "quantity": <virtual currency value>,
	   "user_id": <user id passed to NinthDecimal at moment>,
	   "signature": <signature>
}

Async Virtual Currency Issuance Endpoint

The issuance endpoint will receive commands from NinthDecimal to issue virtual currency in response to later events, such as an application install, survey completion, or purchase. This does require a user_id be set on the NinthDecimal object.

As the message format is different from virtual currency reward redemption messages, it is possible to reuse the endpoint for this purpose.

The JSON POST request will contain the following data:

  • content (string): The unique identifier of the virtual currency (e.g. coins)
  • quantity (int): The quantity of the virtual currency (e.g. 20)
  • user_id (string): A unique field in which to verify the user. This could be any unique identifier, be it a login handle, email address or Advertising ID. This is required if using the virtual currency reward redemption callback.
  • transaction_id (string): The linked reward ID.
  • nonce (string): A string, when combined with the transaction ID, can be used to uniquely reference a message.
  • origin (string): The event that triggered this issuance event (i.e., survey, other).
  • signature (string): The HMAC of the payload. Used to verify valid POSTs. See below for more information.

Verify a virtual currency issuance command using this string containing the following:

sha1(content + quantity + user_id + transaction_id + nonce + origin + app_secret)

Here’s a quick example of how you would verify the request in python:

def is_valid_message(content, quantity, user_id, nonce, origin, transaction_id, signature):
    sign = '%s%s%s%s%s%s%s' % (content, quantity, user_id, transaction_id, nonce, origin, YOUR_APPLICATION_SECRET)
    return hashlib.sha1(sign).hexdigest() == signature

3. Verify the Callback

Verify a virtual currency callback using this string containing the following:

sha1(content + name + quantity + userid + transaction_id + app_secret)

Here’s a quick example of how you would verify the request in python:

def is_valid_request(content, quantity, userid, transaction_id, signature):
    sign = '%s%s%s%s%s' % (content, name, quantity, userid, transaction_id, YOUR_APPLICATION_SECRET)
    return hashlib.sha1(sign).hexdigest() == signature

Display Reward

If a moment is rewardable, the above Moment call will return a JSON document describing the reward. The document contains a key view.modal.body_url that is the URL to the HTML page displaying the reward.

It is recommended to display this URL in an iframe or webview. However, you may display the reward in any way you see fit.

GET <view.modal.body_url> HTTPS/1.1

Example Request

https://api.kiip.me/reward/preview/<reward id>?locale=<locale>&sdk_version=<sdk version>&r=394542&application_id=<application id>&moment_id=<moment id>

e.g.

https://api.kiip.me/reward/preview/509821a1-48cf-59b6-38cd-d7cb63e20cbb?locale=&sdk_version=2.0.9&r=394542&application_id=545ab108-1cc7-86ca-0031-2b8b91b9141f&moment_id=5463e141-93ad-6f92-42e3-25a55294b9ac

Closing the Webview

Typically, Server-to-Server implementations will handle dismissing a reward unit automatically. However, there will be a webview left behind that requires dismissing.

Server-to-Server implementations for iOS and Android require the following code in order to know when to close the webview that contained a dismissed reward unit.

iOS

// UIWebViewDelegate
- (BOOL) webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType 
{
	NSURL *url = request.URL;
	if ([url.scheme isEqualToString:@"kiip"]) {
		if ([url.host isEqualToString:@"did_dismiss"]) {
			NSDictionary *params = [self dictionaryFromQueryString:[url query]];
			// URL -- kiip://did_dismiss/?native_url=<url>
			NSString *urlString = [params objectForKey:@"native_url"];
			// handle closing the reward unit webview
			if (urlString) {
				NSURL *url = [NSURL URLWithString:urlString];
				// handle redirect here
				// See 'Closing Webviews Containing Redirect URLs' below
			}
		}
		return NO;
	}
	return YES;
}

Android

public final class RewardUnitView extends WebView {

	private static final String KIIP_URL_SCHEME = "kiip";
	private static final String KIIP_HOST_DID_DISMISS = "did_dismiss";

	@Override
	public boolean shouldOverrideUrlLoading(final WebView view, String url) {
		Uri uri = Uri.parse(url);
		String urlString = uri.getQueryParameter("native_url");
		if (KIIP_URL_SCHEME.equals(uri.getScheme())) {
			if (KIIP_HOST_DID_DISMISS.equals(uri.getHost())) {
				// handle closing the reward unit webview
				if (urlString) {
					// load the urlString into a new webview. 
					// if urlString fails, return false
				}
			}
		}
		return true;
	}
}

Closing Webviews Containing Redirect URLs

Some rewards will contain a redirect URL to direct the user to upon redeeming and closing the webview. The URL will be presented as a query string parameter, like this:
kiip://did_dismiss?native_url=https://kiip.me

A typical way to handle a redirect URL would be to parse the URL from the query string parameter. If a redirect URL is present, open a new webview or browser window pointing to the redirect url. Ensure you also include logic for closing the reward unit.

If you choose to use a new webview, ensure that the end user is able to close this view.

Detecting Successful Redemptions

The reward unit can be dismissed by either a successful redemption or by the user clicking the close button, a non-redemption. Non-redemptions have close=true in the query string parameter, like this: kiip://did_dismiss?close=true

If the close parameter does not exist, then it was a successful redemption.

Notes about Device IDs

All calls to the NinthDecimal platform must include a valid device ID. In most cases, the device ID should be the device specific Advertiser Identifier that is returned by the mobile operating system.

45e6-4ce34c50-bf87-4b00-6eb1-0ab0d6147788

CCPA Compliance

For CCPA compliance, there are fields in the JSON payload you can provide in both the session endpoint and the moment endpoint. You can use either endpoint to provide us the info. You preferably only need to provide us the values when there are changes to a user status. To match a user in our compliance database, we will look up the user on an explicit match on both the user ID and device ID.

Here is a example of a CCPA update to let us know you are CCPA compliant and that a unique user of your app, johndoe on device ID 45e3-1d799e20-963a-4ddd-8593-0683b30e7d5b from California has opted out of having their data collected.

https://api.kiip.me/2.0/server/moment

Payload
{
	"app”:{
		...
		"dns_compliance":true,
		...
	},
	"device":{
		...
		"id":"45e3-1d799e20-963a-4ddd-8593-0683b30e7d5b",
		...
	},
	...
	"user":{
		...
		"userid":"johndoe"
		...
	},
	...
	"user_consent_info": {
		"user_compliance":true,
		"state":"CA"
	}
	...
}

If the johndoe later changes their consent, please provide us the latest values. For more information on each individual parameter, check the JSON payload documentation earlier on this page.