Postback Setup
Enabling Server Postback
From the AdGem Dashboard navigate to Properties & Apps and choose Edit from the Options drop down menu.
Once on the Properties & Apps menu, scroll down until you see Postback Options. Click on the radio input next to Server Postback.
Several new fields will appear that will allow you to configure your server postback.
Postback Key
The first field is your Postback Key. Copy this key to a secure place as you will need it to implement Postback Hashing. For security reasons, the Postback Key will only be visible once. See the Postback Hashing section below to learn more about this important feature.
Postback URL
Next, you will need to provide a Postback URL hosted on your server. AdGem will send a GET request to the URL you provide every time a conversion occurs from your traffic sources.
The following macros are available for you to use in your postback URL which our server will replace with the actual values.
Note - RAWURLENCODE
All fields are raw URL encoded using PHP rawurlencode()
RFC 3986 (which differs from JavaScript encoding scheme), thereby eliminating any spaces in the postback return. For example:
A {campaign_name} macro of Example App: Sports & Casino - CPE FTD (iOS, INCENT, Free, UK
will be returned as Example%App%3A%20Sports%20%26%20Casino%20-%20CPE%20FTD%20%28iOS%2C%20INCENT%2C%20Free%2C%20UK%29
Notice
IMPORTANT: Be sure to include the payout
field in your Postbacks
We highly recommend that you include payout
in your Postback response so that you have can track the accurate decimal amount of revenue earned from the user completing the offer. The payout
field should be used to validate ANY release of a user's rewards as this is the official and truest amount AdGem will pay for each transaction.
Macro | Required | Description |
---|---|---|
{player_id} | The unique ID of the player/user on your system | |
{app_id} | The unique ID for your app on AdGem | |
{app_version} | The version of your app where the click originated | |
{ad_type} | The type of ad unit where the click originated, for example ‘offerwall’ | |
{amount} | Recommended | The amount of virtual currency to reward the user who completed the offer |
{campaign_id} | The unique ID of the campaign/offer that was completed | |
{campaign_name} | The name of the campaign/offer that was completed as it shows in the AdGem Dashboard | |
{offer_name} | The name of the campaign/offer that was completed as it shows in the Offerwall/API call | |
{city} | The city for the user, where the offer was completed | |
{connection_type} | The type of connection for the user who completed the offer, for example ‘Cellular’ or ‘Cable/DSL’ | |
{click_datetime} | The exact date and time when the user clicked on the offer | |
{conversion_datetime} | The exact date and time when the user completed the offer | |
{country} | The ISO country code for the user, where the offer was completed | |
{device} | The user’s device type, for example ‘Moto E Plus’ | |
{device_name} | The user’s device name | |
{transaction_id} | Recommended | The unique AdGem ID of the offer conversion |
{tracking_type} | The type of offer/campaign completed, for example CPI, CPE, CPA, CPA, CPC, Market Research |
|
{gaid} | Google Advertising ID, available when the developer is using Google Play Services | |
{idfa} | Apple Advertising ID, available when the user has not limited ad tracking | |
{ip} | The IP Address for the user who completed the offer | |
{isp} | The Internet Service Provider for the user who completed the offer | |
{lat} | The latitude value for the user who completed the offer | |
{lon} | The longitude value for the user who completed the offer | |
{useragent} | The User Agent from the user’s default browser app | |
{os_version} | The user’s Operating System version number | |
{payout} | Recommended | The decimal amount of revenue earned from the user completing the offer |
{allow_multiple_conversions} | Indicates if an offer allows for multiple conversions via boolean response | |
{all_goals_completed} | Indicates if all goals have been completed for the campaign | |
{goal_name} | Recommended | Indicates the name of the completed goal (Recommended for Multi-Reward Offers) |
{goal_id} | Recommended | Indicates the unique AdGem goal ID of the completed goal (Recommended for Multi-Reward Offers) |
{platform} | The platform this user’s device is using, for example ios |
|
{state} | The user’s state or region where they are located | |
{player_age} | The user’s age as set by the publisher using the SDK | |
{player_gender} | The user’s gender as set by the publisher using the SDK | |
{player_payer} | A boolean value, as set by the publisher using the SDK | |
{player_iap_total_usd} | A integer value, as set by the publisher using the SDK | |
{player_created_at} | A datetime stamp without the time zone, as set by the publisher using the SDK | |
{player_level} | The level achieved by a player as set by the publisher using the SDK | |
{placement} | A integer value that represents the placement of the ad unit as set by the publisher using the SDK | |
{c1} | A custom parameter value as set by the publisher using the SDK | |
{c2} | A custom parameter value as set by the publisher using the SDK | |
{c3} | A custom parameter value as set by the publisher using the SDK | |
{c4} | A custom parameter value as set by the publisher using the SDK | |
{c5} | A custom parameter value as set by the publisher using the SDK |
Securing Your Postbacks
We provide two different security measures for securing the postback communication between AdGem and publisher: IP Whitelisting and Postback Hashing.
While the implementation of these security checks are entirely at the publisher’s discretion, we strongly recommend that they implement both to ensure the postbacks they receive are genuine and sent by AdGem. In the event that a third party were to get a publisher’s postback url, they could send postback requests that appear to be from AdGem in an attempt to earn large amounts of virtual currency. These security measures, when implemented properly, can protect publishers from this type of fraudulent activity.
IP Whitelisting
The postback will be sent from a static IP address that should be whitelisted on the publisher’s server. Please contact us at [email protected] or your AdGem Publisher Support Advocate to get the IP address used in production.
Postback Hashing
In order to provide an added layer of security to postbacks, AdGem offers Postback Hashing. To enable postback hashing, you must upgrade to v2 of our postback system by generating a Postback Key. Once a key is generated, publishers will begin receiving two new query string parameters in their postbacks: request_id
and verifier
.
The request_id
is a UUID that uniquely identifies each postback. You should never receive two postbacks with the same request_id
.
The verifier
parameter is a cryptographic hash that we generate using the secret postback key provided in the AdGem Dashboard and the postback url.
Publishers should use this field to verify the validity of every postback they receive from AdGem. To do this they should remove the verifier
parameter from the end of the postback url. Next they should perform a hmac hash of the remaining url using the SHA256 algorithm and their postback secret key. Finally, compare the generated hash with the one provided in the verifier
parameter, if they match then you can be assured that the request has not been tampered with.
Code Sample
// securely supply the static whitelist ip and your secret postback key using env variables
define('ADGEM_WHITELIST_IP', $_ENV['ADGEM_WHITELIST_IP']);
define('ADGEM_POSTBACK_KEY', $_ENV['ADGEM_POSTBACK_KEY']);
// verify the static IP
if(ADGEM_WHITELIST_IP !== $_SERVER['REMOTE_ADDR']) {
http_response_code(403);
exit('Error: '.$_SERVER['REMOTE_ADDR'].' does not match the whitelisted IP address.');
}
// get the full request url
$protocol = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on' ? "https" : "http");
$request_url = "$protocol://$_SERVER[HTTP_HOST]$_SERVER[REQUEST_URI]";
// parse the url and query string
$parsed_url = parse_url($request_url);
parse_str($parsed_url['query'], $query_string);
// get the verifier value
$verifier = $query_string['verifier'] ?? null;
if (is_null($verifier)) {
http_response_code(422);
exit("Error: missing verifier");
}
// rebuild url without the verifier
unset($query_string['verifier']);
$hashless_url = $protocol.'://'.$parsed_url['host'].$parsed_url['path'].'?'.http_build_query($query_string, "", "&", PHP_QUERY_RFC3986);
// calculate the hash and verify it matches the provided one
$calculated_hash = hash_hmac('sha256', $hashless_url, ADGEM_POSTBACK_KEY);
if ($calculated_hash !== $verifier) {
http_response_code(422);
exit('Error: invalid verifier');
}
// valid, it is safe to process the postback
http_response_code(200);
exit('OK');
const http = require('http');
const { URL } = require('url');
const CryptoJS = require("crypto-js");
http.createServer(function (req, res) {
// verify the static ip
let ip = req.socket.remoteAddress;
if (ip != process.env.ADGEM_WHITELIST_IP) {
res.writeHead(422, { 'Content-Type': 'text/html' });
return res.end('Error: ' + ip + ' does not match the whitelisted IP address.');
}
// get the full request url
let protocol = (req.connection.encrypted ? 'https': 'http');
let requestUrl = new URL(protocol + '://' + req.headers.host + req.url);
// get the verifier value
let verifier = requestUrl.searchParams.get('verifier');
if (verifier == undefined) {
res.writeHead(422, { 'Content-Type': 'text/html' });
return res.end("Error: missing verifier");
}
// remove the verifier
requestUrl.searchParams.delete('verifier');
// calculate the hash and verify it matches the provided onea
let hash = CryptoJS.HmacSHA256(requestUrl.href, process.env.ADGEM_POSTBACK_KEY).toString(CryptoJS.enc.Hex);
if (hash !== verifier) {
res.writeHead(422, { 'Content-Type': 'text/html' });
return res.end("Error: invalid verifier");
}
// valid, it is safe to process the postback
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end('OK');
}).listen(process.env.PORT, '0.0.0.0');
class PostbacksController < ApplicationController
before_action :check_ip
def store
if params[:verifier].blank?
logger.error "verifier is blank in url: #{request.original_url}"
return render status: 403, json: {'success': false}
end
# remove verifier from url and keep order of params
hashless_url = request.original_url.split('&verifier=')[0]
calculated_hash = OpenSSL::HMAC.hexdigest("SHA256", ENV['POSTBACK_SECRET_KEY'], hashless_url)
if calculated_hash != params[:verifier]
return render status: 403, json: {'success': false}
end
# valid, it is safe to process the postback
# do your happy dance and add custom code for processing here
return render status: 200, json: {'success': true}
end
private
# verify whitelist ip
def check_ip
unless request.remote_ip == ENV['ADGEM_WHITELIST_IP']
logger.error "#{request.remote_ip} is not whitelisted in url: #{request.original_url}"
return render status: 403, json: {'success': false}
end
end
end
Updated on April 5, 2023