Skip to content

Postback Methods

AdGem can provide Postbacks to you either via GET Request method or POST Request method. By default, AdGem is set to send postbacks via GET request unless otherwise enabled by your Publisher Support Advocate.

Please note that the POST request postback method is currently in Beta status. If you would like to join the Beta program, reach out to your dedicated Publisher Support Advocate to get started.


GET Requests (Default)

Enabling Server Postback

From the AdGem Dashboard navigate to Properties & Apps and choose Edit from the Options drop down menu.

Screenshot

Once on the Properties & Apps menu, scroll down until you see Postback Options. Click on the radio input next to Server Postback.

Screenshot

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.

Screenshot

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.

Screenshot

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

Best Practices

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.

IMPORTANT: Tracking Player Progress on Multi-Reward Offers

For tracking user progress of Multi-Reward offers, we recommend that you include the {goal_id} macro in your Postback URL. Each goal has a unique ID in AdGem and is immutable in the API call response. This way you can easily determine which goals your players have completed to better track player progress.

IMPORTANT: Ordering Postback Macro Parameters

Because some frameworks sort parameters when handling query strings, we recommend alphabetizing your query string by field to avoid issues with authentication.

Macro
Required
Description
{ad_type} The type of ad unit where the click originated, for example ‘offerwall’
{all_goals_completed} Indicates if all goals have been completed for the campaign
{allow_multiple_conversions} Indicates if an offer allows for multiple conversions via boolean response
{amount} Recommended The amount of virtual currency to reward the user who completed the offer
{app_id} The unique ID for your app on AdGem
{app_version}^ The version of your app where the click originated
{c1}^ A custom parameter value as set by the publisher
{c2}^ A custom parameter value as set by the publisher
{c3}^ A custom parameter value as set by the publisher
{c4}^ A custom parameter value as set by the publisher
{c5}^ A custom parameter value as set by the publisher
{campaign_id} Recommended The unique ID of the campaign/offer that was completed
{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
{gaid}^ Google Advertising ID, available when the developer is using Google Play Services
{goal_id} Recommended Indicates the unique ID of each goal in a campaign/offer that was completed (Recommended for Multi-Reward Offers)
{goal_name} Indicates the name of the completed goal
{idfa}^ Apple Advertising ID, available when the user has not limited ad tracking
{ip}^ The IP Address for the user who completed the offer
{offer_id} The unique offer version with which the user engaged (only applicable for Offer API integrations)
{offer_name} The name of the campaign/offer that was completed as it shows in the Offerwall/API call
{os_version}^ The user’s Operating System version number
{payout} Recommended The decimal amount of revenue earned from the user completing the offer
{platform}^ The platform this user’s device is using, for example ios
{player_id} The unique ID of the player/user on your system
{state}^ The user’s state or region where they are located
{store_id} The unique ID of the app campaign, as assigned by the Google Play Store or Apple App Store
{tracking_type} The type of offer/campaign completed, for example CPI, CPE, CPA, CPA, CPC, Market Research
{transaction_id} Recommended The unique AdGem ID of the offer conversion
{useragent}^ The User Agent from the user’s default browser app

Index

^ Indicates that the postback macro data is provided by the Publisher in the click_url. AdGem will pass these values back to you in the postback that we send for player event completion.

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 an 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.

hash_hmac(‘sha256’, $postback_url, $postback_secret_key)
CryptoJS.HmacSHA256(postbackUrl, postbackSecretKey).toString(CryptoJS.enc.Hex)
OpenSSL::HMAC.hexdigest(SHA256, postback_secret_key, postback_url)

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

POST Requests

Beta

Joining the Beta

V3 postbacks are in a beta status. If you would like to join the beta program, reach out to your dedicated Publisher Support Advocate.

The Postback URL can be set in the AdGem Dashboard. Instructions on how to input your Postback URL into the AdGem Dashboard can be found above. AdGem can send postbacks for Reward events and and Install events.

Reward Postbacks

These postbacks correspond to a player converting on a rewarded goal. Most offer goals will be of this type.

The conversion_type will indicate this with a value of "reward".

Install Postbacks

Note

By default, AdGem only sends postbacks on Payable Conversion events. If you would like to enable Install postbacks to be sent to you, please reach out to your dedicated Publisher Support Advocate so we can enable this for you!

These postbacks correspond to a player completing an install goal. These install goals are used primarily for tracking purposes, and they do not reward the player with in-game currency.

The conversion_type will indicate this with a value of "install".

Receiving AdGem Postbacks via POST Method

A POST request will be made to the postback url defined in your AdGem publisher dashboard.

Screenshot

Here is an example of the request body:

{
    "request_id": "01786456-b959-404a-baa7-05ef8a2e0290",
    "timestamp": "2024-07-11T20:26:10.344522Z",
    "data": {
        "app_id": "2",
        "campaign_id": "1",     
        "player_id": "bernhard.edison",
        "amount": 150,
        "payout": 1.5,
        "all_goals_completed": 1,
        "conversion_id": "c5eb2a9d-41a4-4088-80bb-ebc87bd1bb62",
        "app_version": "1.0",
        "ad_type": "offerwall",
        "country": "US",
        "c1": "custom value 1",
        "c2": "custom value 2",
        "c3": "custom value 3",
        "c4": "custom value 4",
        "c5": "custom value 5",
        "gaid": "bk9384xs-p449-96ds-r132",
        "idfa": "AB1234CD-E123-12FG-J123",
        "ip": "24.24.24.25",
        "os_version": "10.0.0",
        "platform": "Other",
        "state": "New York",
        "click_datetime": "2024-07-15 10:39:55",
        "conversion_datetime": "2024-07-15 10:39:55",
        "goal_name": "Reach level 20",
        "goal_id": "12345678911123456",
        "offer_name": "Romaguera-Harber",
        "tracking_type": "CPA",
        "allow_multiple_conversions": "false",
        "store_id": "com.whatever.example",
        "offer_id": "12345678900123456",
        "request_id": "6bfc84d8-5d9a-4964-bba4-0fd2c2ed1563",
        "conversion_type": "reward"
    }
}

Body Response Fields:

Field
Description
request_id A unique ID for this postback request
timestamp The timestamp in UTC of when the postback was sent
app_id The unique ID for your app on AdGem
campaign_id The unique ID of the campaign/offer that was completed
player_id The unique ID of the player/user on your system
amount The amount of virtual currency to reward the user who completed the offer
payout The decimal amount of revenue earned from the user completing the offer
all_goals_completed Indicates if all goals have been completed for the campaign
conversion_id The unique AdGem ID of the offer conversion
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)
country The location (i.e., the ISO country code) of the user when the offer was completed
c1 A custom parameter value as set by the publisher
c2 A custom parameter value as set by the publisher
c3 A custom parameter value as set by the publisher
c4 A custom parameter value as set by the publisher
c5 A custom parameter value as set by the publisher
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
os_version The user’s operating system version number
platform The platform this user’s device is using (for example, iOS)
state The user’s state or region where they are located
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
goal_name Indicates the name of the completed goal
goal_id Indicates the unique ID of each goal in a campaign/offer that was completed
offer_name The name of the campaign/offer that was completed as it shows in the Offerwall/API call
tracking_type The type of offer/campaign completed (for example, CPI, CPE, CPA, CPA, CPC, Market Research)
allow_multiple_conversions Indicates if an offer allows for multiple conversions via boolean response
store_id The unique ID of the app campaign, as assigned by the Google Play Store or Apple App Store
offer_id The unique offer version with which the user engaged
conversion_type The type of the conversion (for example, "install" when the amount is 0, otherwise "reward")

Verifying Authenticity

To authenticate the request, hash the request body and the secret key using the HMAC-SHA256 algorithm. Compare the result to the provided Signature header value to confirm a match.

Examples:

const express = require('express');
const crypto = require('crypto');

const app = express();
const port = 3000;

app.use(express.json());

app.post('/postbacks/adgem/v3', (req, res) => {
    const receivedSignature = req.headers['Signature'];

    const postData = JSON.stringify(req.body);
    const expectedSignature = crypto.createHmac('sha256', 'secret-key').update(postData, 'utf8').digest('hex');

    if (expectedSignature === receivedSignature) {
        res.status(200).send();
    } else {
        res.status(401).send();
    }
});

app.listen(port, () => {
    console.log(`Server running on port ${port}`);
});
Route::post('/postbacks/adgem/v3', function () {
    $receivedSignature = request()->headers->get('Signature');

    $expectedSignature = hash_hmac('sha256', request()->getContent(), 'secret-key');

    if ($expectedSignature === $receivedSignature) {
        return response()->noContent(200);
    } else {
        return response()->noContent(401);
    }
});

Updated on November 20, 2024