Learn how to track clicks, report installation events, and securely encrypt your postbacks to prevent conversion fraud.
The Adzo Grow Tracking System allows advertisers to verify completions. The user flow is structured as follows:
click_id query parameter.click_id and your private security credentials.
When setting up a Campaign, provide your Store or Website URL containing the {click_id} macro. We automatically replace this macro with a unique 26-character identifier.
https://play.google.com/store/apps/details?id=com.app.name&referrer=click_id%3D{click_id}
To authenticate postbacks, you must authorize calls using either a Direct Token match or a Signed MD5 Hash Signature.
| Parameter | Type | Location | Description |
|---|---|---|---|
| click_id | string | Query / Body | Required. The click identifier received during the initial user click redirection. |
| event or event_id | string | Query / Body | Required. The specific conversion step/event identifier (e.g. install, registration). Must match one of the campaign events exactly. |
| security_token | string | Query / Header | Required (Method A). Your campaign's unique Security Token. Can be passed as a query string parameter or via the X-Security-Token HTTP header. |
| signature | string | Query / Body | Required (Method B - Recommended). Direct MD5 signature generated as: md5(click_id + security_token). Alternative to sending the raw token. |
The postback signature uses a secure MD5 hashing algorithm. The input components are concatenated as a raw string in the exact sequence shown below:
<?php
// Define conversion credentials
$click_id = "01KWTYWVFMEYTM4RGCZ2QR3SK4"; // Received during user redirection
$event = "install"; // Event identifier (must match events tab)
$security_token = "your_campaign_security_token"; // Campaign Security Token
// Calculate security signature
$signature = md5($click_id . $security_token);
// Postback endpoint URL
$url = "https://clipzaar.com/postback/callback?" . http_build_query([
'click_id' => $click_id,
'event' => $event,
'signature' => $signature
]);
// Execute HTTP GET request
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
// Parse and display response
if ($http_code === 200 && $response) {
$data = json_decode($response, true);
if (isset($data['status']) && $data['status'] === 'success') {
echo "Success: " . ($data['message'] ?? 'Conversion tracked successfully!') . "\n";
} else {
echo "Failed: " . ($data['message'] ?? 'Unknown API error') . "\n";
}
} else {
echo "Connection Error: HTTP Status " . $http_code . "\n";
}
?>
const axios = require('axios');
const crypto = require('crypto');
// Define conversion credentials
const clickId = "01KWTYWVFMEYTM4RGCZ2QR3SK4";
const eventName = "install";
const securityToken = "your_campaign_security_token";
// Calculate MD5 hash signature
const signature = crypto
.createHash('md5')
.update(clickId + securityToken)
.digest('hex');
// Trigger the postback request
const url = `https://clipzaar.com/postback/callback`;
axios.get(url, {
params: {
click_id: clickId,
event: eventName,
signature: signature
},
timeout: 10000
})
.then(response => {
const data = response.data;
if (data.status === 'success') {
console.log('Success:', data.message || 'Conversion tracked successfully!');
} else {
console.log('Failed:', data.message || 'Unknown API error');
}
})
.catch(error => {
console.error('Connection failed:', error.message);
});
import hashlib
import requests
# Define conversion credentials
click_id = "01KWTYWVFMEYTM4RGCZ2QR3SK4"
event = "install"
security_token = "your_campaign_security_token"
# Calculate the MD5 signature
signature = hashlib.md5((click_id + security_token).encode('utf-8')).hexdigest()
# Send GET request to the postback callback endpoint
url = "https://clipzaar.com/postback/callback"
params = {
'click_id': click_id,
'event': event,
'signature': signature
}
try:
response = requests.get(url, params=params, timeout=10)
if response.status_code == 200:
data = response.json()
if data.get('status') == 'success':
print(f"Success: {data.get('message', 'Conversion tracked successfully!')}")
else:
print(f"Failed: {data.get('message', 'Unknown API error')}")
else:
print(f"Connection Error: HTTP Status {response.status_code}")
except requests.exceptions.RequestException as e:
print(f"Postback request failed: {e}")
click_id) that maps to the advertiser's campaign. Postbacks matching non-existent or fabricated click_ids are immediately rejected.
click_id and event parameters are intercepted, prevented from double-crediting balances, and logged as duplicate conversion attempts.
Learn how to fetch JSON feeds, bind reward widgets, and set up secure callback listeners with MD5/SHA256 HMAC signature verification.
The easiest way to integrate the Adzo Reward Wall is to load the visual UI directly using an HTML <iframe> on websites, or in a WebView inside your native Android/iOS mobile application.
<iframe src="https://clipzaar.com/offerwall/render?appid=1&user_id=test_player_1" width="100%" height="800px" style="border:none; border-radius:16px;"></iframe>
For native mobile apps (Android/iOS) or custom reward screens, you can programmatically fetch the offer feed as JSON. This allows you to construct a custom user interface using your own styling while tracking conversions correctly.
| Query Parameter | Type | Status | Description |
|---|---|---|---|
| appid | integer | Required | Your Offerwall Application ID. |
| user_id | string | Required | A unique ID matching the active user (used to generate valid click redirect links). |
| platform | string | Optional | Filter campaigns by operating system: android, ios, or desktop. |
| type | string | Optional |
Filter campaigns by conversion step category or status:
|
[
{
"id": 2,
"name": "Word Mastermind iOS Game",
"description": "Install and complete puzzle events to earn reward points.",
"platform": "ios",
"package_id": "com.word.mastermind",
"payout_usd": 1.20,
"points_reward": 120,
"icon_url": "https://clipzaar.com/storage/offerwalls/logos/default-icon.png",
"click_url": "https://clipzaar.com/click?campaign_id=2&pub_id=1&click_id=01KWTY...",
"events": [
{
"name": "Install and open application",
"points": 50
},
{
"name": "Reach Level 10",
"points": 70
}
]
}
]
When configuring your Postback URL inside the details screen, you can embed any of the following macros. We automatically resolve and bind parameters before triggering your endpoint.
| Macro Tag | Description | Resolved Value Example |
|---|---|---|
{user_id} |
The custom user identifier passed in the offerwall render link query parameter. | test_player_1 |
{payout} |
The revenue payout earned by the publisher (formatted in USD, 4 decimals). | 1.2000 |
{points} |
The virtual coins amount calculated based on your exchange rate and rounding settings. | 1200 |
{offer_id} |
The unique identifier of the completed offer/campaign. | 15 |
{offer_name} |
The title of the completed offer/campaign. | Survey Completion |
{transaction_id} |
The unique transaction / conversion identifier (ULID). | 01J234567890ABCDEFGHJKMNPQ |
{ip} |
The IP address of the user who completed the offer. | 192.168.1.50 |
{signature} |
MD5 verification hash computed using your API Secret Key. | 32-char hexadecimal hash |
{sig} |
HMAC SHA256 secure hash signature computed using your API Secret Key. | 64-char hexadecimal hash |
To authenticate postback callbacks, verify either the {signature} (MD5) or {sig} (HMAC-SHA256) signature query parameter on your server using your private API Secret Key.
Below is an example of a Publisher's Postback URL template containing macros, followed by the exact GET request compiled and executed by Adzo's backend:
<?php
// 1. Retrieve query parameters sent by Adzo
$userId = isset($_GET['user_id']) ? $_GET['user_id'] : '';
$payout = isset($_GET['payout']) ? $_GET['payout'] : '';
$points = isset($_GET['points']) ? $_GET['points'] : '';
$offerId = isset($_GET['offer_id']) ? $_GET['offer_id'] : '';
$offerName = isset($_GET['offer_name']) ? $_GET['offer_name'] : '';
$transactionId = isset($_GET['transaction_id'])? $_GET['transaction_id']: '';
$userIp = isset($_GET['ip']) ? $_GET['ip'] : '';
$receivedSig = isset($_GET['sig']) ? $_GET['sig'] : ''; // HMAC-SHA256
$receivedMd5 = isset($_GET['signature']) ? $_GET['signature'] : ''; // MD5
// Your unique Offerwall API Secret Key
$apiSecretKey = "your_api_secret_key_here";
if (!$userId || !$payout) {
http_response_code(400);
echo json_encode(["status" => "error", "message" => "Missing required parameters"]);
exit;
}
// Format payout exactly to 4 decimals with dot separator (e.g. "1.2000")
$payoutFormatted = number_format((float)$payout, 4, '.', '');
$isValid = false;
if ($receivedSig) {
// Verify using HMAC-SHA256
$dataString = $userId . ':' . $payoutFormatted;
$expectedSig = hash_hmac('sha256', $dataString, $apiSecretKey);
if (hash_equals($expectedSig, $receivedSig)) {
$isValid = true;
}
} elseif ($receivedMd5) {
// Verify using MD5
$dataString = $userId . $payoutFormatted . $apiSecretKey;
$expectedMd5 = md5($dataString);
if (hash_equals($expectedMd5, $receivedMd5)) {
$isValid = true;
}
}
if ($isValid) {
// ----------------------------------------------------
// TODO: Perform your database operations here:
// 1. Log transaction $transactionId to prevent duplicate crediting.
// 2. Award $points to user $userId for completing offer $offerName ($offerId).
// ----------------------------------------------------
header('Content-Type: application/json');
echo json_encode(["status" => "success", "message" => "Callback processed"]);
} else {
http_response_code(400);
header('Content-Type: application/json');
echo json_encode(["status" => "error", "message" => "Signature mismatch"]);
}
?>
const express = require('express');
const crypto = require('crypto');
const app = express();
app.get('/postback/listener', (req, res) => {
const { user_id, click_id, payout, points, sig, signature } = req.query;
const apiSecretKey = 'your_api_secret_key_here';
if (!user_id || !payout) {
return res.status(400).json({ status: 'error', message: 'Missing parameters' });
}
// Format payout exactly to 4 decimals with dot separator
const payoutFormatted = parseFloat(payout).toFixed(4);
let isValid = false;
if (sig) {
// Verify using HMAC-SHA256
const dataString = `${user_id}:${payoutFormatted}`;
const expectedSig = crypto
.createHmac('sha256', apiSecretKey)
.update(dataString)
.digest('hex');
if (crypto.timingSafeEqual(Buffer.from(expectedSig), Buffer.from(sig))) {
isValid = true;
}
} else if (signature) {
// Verify using MD5
const dataString = `${user_id}${payoutFormatted}${apiSecretKey}`;
const expectedMd5 = crypto
.createHash('md5')
.update(dataString)
.digest('hex');
if (expectedMd5 === signature) {
isValid = true;
}
}
if (isValid) {
// ----------------------------------------------------
// TODO: Perform database queries here:
// 1. Check if unique transaction_id has already been rewarded.
// 2. Award user_id the designated points for offer_name (offer_id).
// ----------------------------------------------------
return res.status(200).json({ status: 'success', message: 'Callback processed' });
} else {
return res.status(400).json({ status: 'error', message: 'Signature mismatch' });
}
});
import hmac
import hashlib
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/postback/listener', methods=['GET'])
def postback_listener():
user_id = request.args.get('user_id')
payout = request.args.get('payout')
points = request.args.get('points')
offer_id = request.args.get('offer_id')
offer_name = request.args.get('offer_name')
transaction_id = request.args.get('transaction_id')
user_ip = request.args.get('ip')
received_sig = request.args.get('sig') # HMAC-SHA256
received_md5 = request.args.get('signature') # MD5
if not user_id or not payout:
return jsonify({'status': 'error', 'message': 'Missing parameters'}), 400
api_secret_key = b'your_api_secret_key_here'
# Format payout exactly to 4 decimal places
payout_formatted = "{:.4f}".format(float(payout))
is_valid = False
if received_sig:
# Verify using HMAC-SHA256
data_string = f"{user_id}:{payout_formatted}".encode('utf-8')
expected_sig = hmac.new(api_secret_key, data_string, hashlib.sha256).hexdigest()
if hmac.compare_digest(expected_sig, received_sig):
is_valid = True
elif received_md5:
# Verify using MD5
raw_key = api_secret_key.decode('utf-8')
data_string = f"{user_id}{payout_formatted}{raw_key}"
expected_md5 = hashlib.md5(data_string.encode('utf-8')).hexdigest()
if hmac.compare_digest(expected_md5, received_md5):
is_valid = True
if is_valid:
# ----------------------------------------------------
# TODO: Perform database queries here:
# 1. Log transaction_id to prevent duplicate crediting.
# 2. Credit the points to the user_id's account for completing offer_name.
# ----------------------------------------------------
return jsonify({'status': 'success', 'message': 'Callback processed'}), 200
else:
return jsonify({'status': 'error', 'message': 'Signature mismatch'}), 400
{"status":"success"}) to prevent balance inflation while acknowledging our notification system.