Learn how to track clicks with dynamic macros, configure multi-event conversion milestones, and send authenticated postback callbacks with zero fraud.
The PubAdzo V2 Grow platform connects your mobile apps, games, and web campaigns to verified publisher traffic. Our tracking architecture follows a high-performance, real-time Server-to-Server (S2S) conversion lifecycle:
The user selects your offer on the Reward Wall. PubAdzo V2 creates a unique 26-character click_id (ULID) and redirects the user to your Tracking URL.
The user installs your app, opens it, registers an account, or reaches a targeted milestone (e.g. Level 10, First Deposit) specified in your campaign setup.
Your server or Mobile Measurement Partner (AppsFlyer, Adjust, Singular) fires a GET or POST callback to PubAdzo V2 with the click_id and event key.
PubAdzo V2 validates the security signature, records the conversion, deduces your campaign budget, and rewards the user/publisher.
When configuring your campaign in PubAdzo V2 Grow, provide your Destination / Tracking URL (e.g. Google Play Store URL, Apple App Store URL, or Mobile Measurement Partner tracking link). You can embed dynamic macros directly into your URL template:
| Macro | Type | Description | Example Replaced Value |
|---|---|---|---|
{click_id} |
string (ULID) | Essential. Unique 26-character universally unique identifier generated per click. You must store this value and return it in your conversion postback. | 01JNW9TYW8E9QMTXYZ01234567 |
{campaign_id} |
integer | The unique numeric identifier of your campaign in PubAdzo V2. | 42 |
{pub_id} or {publisher_id} |
integer | The unique Offerwall / Publisher ID generating the click. | 7 |
If you accidentally omit {click_id} from your Tracking URL template, the PubAdzo V2 redirection engine automatically detects its absence and safely appends ?click_id={click_id} (or &click_id={click_id}) to your destination URL. This ensures you never lose click attribution!
https://play.google.com/store/apps/details?id=com.yourcompany.app&referrer=click_id%3D{click_id}%26campaign_id%3D{campaign_id}
https://app.appsflyer.com/com.yourcompany.app?pid=clipzaar&click_id={click_id}&c={campaign_id}
https://example.com/signup?click_id={click_id}&utm_source=clipzaar&utm_campaign={campaign_id}
PubAdzo V2 Grow supports both Single-Event and Multi-Event (Multi-Reward) campaigns. Each milestone is configured with its own unique event_id and CPA/CPI bid:
Ideal for standard CPI or simple signups. The campaign contains 1 target event (e.g. install or signup). Once reported, the offer is marked as completed for that user.
Ideal for mobile games and apps with retention funnels. You define sequential steps (e.g. install for $0.20, level_10 for $0.80, deposit for $5.00). Each event triggers its own postback.
In every postback callback you send, the event or event_id parameter must exactly match an event_id defined in your Campaign Events. If you send an unregistered event ID, the backend returns: {"status": "error", "message": "Invalid event_id or event."}.
When a user completes an installation or milestone, your server or MMP must trigger the PubAdzo V2 Postback Callback URL. Both HTTP GET and HTTP POST (JSON, Form-Data, or Query String) are natively supported.
| Parameter | Type | Location | Requirement | Description |
|---|---|---|---|---|
| click_id | string (ULID) | Query / Body | Required | The 26-character unique click identifier received during user redirection. |
| event or event_id | string | Query / Body | Required | The event step identifier (e.g. install, registration, level_5). Must match one of your campaign events. |
| security_token | string | Query / Body / Header | Method A (Recommended) | Your campaign's private Security Token. Passed directly in the URL query string (e.g. ?security_token=...), in the POST request body, or via the X-Security-Token HTTP header. |
| signature | string (32 hex) | Query / Body | Method B (Alternative) | MD5 signature calculated as md5(click_id + security_token). Optional alternative if you prefer not to expose the raw token in the URL. |
| txid | string | Query / Body | Optional | External conversion / transaction UUID from your MMP or internal database. Used to block replay and duplicate conversion attempts. |
PubAdzo V2 Grow allows you to send postbacks using either Direct Token (Method A - Recommended) or an MD5 Signature (Method B):
This is the standard method used by advertisers, MMPs, and backend webhooks. Simply append your click_id, event, and security_token directly into the query string:
// 1. Conversion credentials
const clickId = '01JNW9TYW8E9QMTXYZ01234567'; // Received during user redirection
const eventName = 'install'; // Must match one of your campaign event IDs
const securityToken = 'your_campaign_security_token'; // From Campaign Details
// 2. Build URL query parameters
const queryParams = new URLSearchParams({
click_id: clickId,
event: eventName,
security_token: securityToken
}).toString();
const baseUrl = 'https://clipzaar.com/postback/callback';
const postbackUrl = baseUrl.includes('?') ? `${baseUrl}&${queryParams}` : `${baseUrl}?${queryParams}`;
console.log(`[Grow Postback] Firing URL: ${postbackUrl}`);
// 3. Fire HTTP GET request
fetch(postbackUrl)
.then(res => res.json())
.then(data => {
if (data.status === 'success') {
console.log('Conversion recorded:', data.message);
} else {
console.error('Postback rejected:', data.message);
}
})
.catch(err => {
console.error('Connection failed:', err.message);
});
<?php
// 1. Conversion credentials
$click_id = "01JNW9TYW8E9QMTXYZ01234567"; // Received during user redirection
$event = "install"; // Must match one of campaign event IDs
$security_token = "your_campaign_security_token"; // From Campaign Details
// 2. Build query parameters
$queryParams = http_build_query([
'click_id' => $click_id,
'event' => $event,
'security_token' => $security_token,
]);
$postbackUrl = "https://clipzaar.com/postback/callback?" . $queryParams;
// 3. Fire HTTP GET request
$response = file_get_contents($postbackUrl);
$result = json_decode($response, true);
if (($result['status'] ?? '') === 'success') {
echo "Success: " . ($result['message'] ?? 'Conversion recorded');
} else {
echo "Failed: " . ($result['message'] ?? 'Verification error');
}
?>
import requests
click_id = '01JNW9TYW8E9QMTXYZ01234567'
event_name = 'install'
security_token = 'your_campaign_security_token'
# 1. Define query parameters
params = {
'click_id': click_id,
'event': event_name,
'security_token': security_token,
}
url = 'https://clipzaar.com/postback/callback'
# 2. Fire GET request
try:
response = requests.get(url, params=params, timeout=10)
data = response.json()
if data.get('status') == 'success':
print('Conversion recorded:', data.get('message'))
else:
print('Postback rejected:', data.get('message'))
except requests.exceptions.RequestException as e:
print('Connection error:', e)
# Direct HTTP GET Query
curl -X GET "https://clipzaar.com/postback/callback?click_id=01JNW9TYW8E9QMTXYZ01234567&event=install&security_token=YOUR_SECURITY_TOKEN"
# Or HTTP POST with JSON body
curl -X POST "https://clipzaar.com/postback/callback" \
-H "Content-Type: application/json" \
-d '{"click_id": "01JNW9TYW8E9QMTXYZ01234567", "event": "install", "security_token": "YOUR_SECURITY_TOKEN"}'
# Or HTTP POST with X-Security-Token header
curl -X POST "https://clipzaar.com/postback/callback" \
-H "Content-Type: application/json" \
-H "X-Security-Token: YOUR_SECURITY_TOKEN" \
-d '{"click_id": "01JNW9TYW8E9QMTXYZ01234567", "event": "install"}'
If your security policies require not exposing the raw security token in URL queries or access logs, you can calculate an MD5 hash signature locally on your server:
The PubAdzo V2 postback endpoint always responds with an HTTP 200 OK status code containing a structured JSON response object. Check the status field (success or error) to verify execution:
{
"status": "success",
"message": "Conversion processed successfully."
}
{
"status": "error",
"message": "Security verification failed."
}
| Backend Message | Cause | Resolution |
|---|---|---|
Missing click_id |
The request did not supply a click_id parameter in query string or body. |
Ensure your postback captures and passes the click_id value originally sent in the user's click redirect. |
Missing event_id or event |
The request did not provide the event or event_id parameter. |
Pass the event name (e.g. event=install) corresponding to your campaign's target event. |
Missing security_token or signature |
Neither security_token nor signature was included in the request. |
Provide either your raw token (security_token or X-Security-Token header) or the calculated MD5 signature. |
Invalid click_id |
The click_id does not exist in the database (fake, expired, or test ID). |
Only postback real click IDs generated by PubAdzo V2 during offer clicks. |
Security verification failed. |
The security token or MD5 signature does not match your campaign's private token. | Verify that you are using the correct Campaign Security Token from your campaign overview and that the hash format is md5(click_id + token). |
Campaign is not active. |
The campaign is currently in pending, paused, draft, or rejected state. |
Ensure your campaign has been reviewed and approved, and has not been manually paused in your dashboard. |
Invalid event_id or event. |
The specified event ID does not exist under this campaign. | Check the Campaign Events list in your campaign overview to ensure the event string matches your setup exactly (case-sensitive). |
Campaign budget exhausted. |
The campaign has reached its total budget limit (spent >= total_budget). |
Add funds or increase the total campaign budget in your Grow dashboard to reactivate tracking. |
Campaign daily budget reached. |
Conversions today have reached your configured daily spending cap. | The campaign will automatically resume accepting conversions at midnight UTC, or you can raise your daily budget limit. |
Campaign is in cooldown/hidden for this user. |
The user is currently in a cooldown period for this campaign. | Normal behavior for campaigns with cooldown limits configured. |
User has already completed this campaign. |
The user has completed the campaign the maximum number of times allowed by your availability setting. | Prevents users from re-completing single-run campaigns. If you want repeat conversions, set availability to Repeatable. |
Conversion for this event already logged previously. |
Duplicate postback attempt detected for this specific click_id and event (or duplicate txid). |
PubAdzo V2 automatically rejects replay attacks. Each event step can be converted once per click. |
Follow this step-by-step procedure to test your postback integration before launching live traffic:
Navigate to your campaign details page (Grow > Campaigns). Under the Integration Links section, copy your Click Tracking URL, Callback URL, and Security Encryption Token.
When you or a test user clicks your offer link on the offerwall, capture the generated click_id query parameter from the redirection URL.
Send your test HTTP postback via cURL or Postman. The server should return {"status":"success","message":"Conversion processed successfully."} and your campaign's conversion counter and spend metrics will increment in real time.
If you use an attribution partner such as AppsFlyer, Adjust, Singular, or Branch, configure their Server-to-Server (S2S) postback using the following template mappings:
| MMP | Tracking URL Parameter | S2S Postback URL Template |
|---|---|---|
| AppsFlyer | &click_id={click_id} |
https://clipzaar.com/postback/callback?click_id=(clickid)&event=(event-name)&security_token=YOUR_TOKEN |
| Adjust | &s2s=1&click_id={click_id} |
https://clipzaar.com/postback/callback?click_id={click_id}&event={event_name}&security_token=YOUR_TOKEN |
| Singular | &clki={click_id} |
https://clipzaar.com/postback/callback?click_id={clki}&event={EVENT_NAME}&security_token=YOUR_TOKEN |
click_id) tied to the campaign, IP, user-agent, and country. Fabricated or random IDs are rejected instantly.
click_id and event are intercepted and denied to prevent double-spending your budget.
txid) Tracking: If provided, external transaction IDs are indexed and verified across the entire campaign to stop malicious replay attacks across different requests.
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 PubAdzo V2 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 track user progress and generate valid click redirect links). |
| type | string | Optional |
Filter campaigns by category, conversion steps, or user status:
|
{
"status": "success",
"offerwall": {
"id": 1,
"name": "My Android Offerwall",
"currency": "Coins",
"exchange_rate": 100
},
"offers": [
{
"campaign_id": 12,
"name": "Fantasy Kingdom RPG",
"description": "Install and reach Level 10 to earn coins.",
"platform": "android",
"reward_points": 120,
"payout_usd": 1.2000,
"category": "game",
"category_label": "Game",
"step_type": "multiple_step",
"step_type_label": "Multiple Steps",
"user_status": "available",
"user_status_label": "Available",
"completed_count": 0,
"max_completions": 1,
"availability": "once",
"icon_url": "https://clipzaar.com/storage/campaigns/icons/fantasy.png",
"banner_url": "https://clipzaar.com/storage/campaigns/banners/fantasy.png",
"steps": [
{
"event_id": 24,
"event_key": "install",
"name": "Install and open app",
"description": "Open game for the first time",
"payout_usd": 0.5000,
"points": 50,
"completed": false
},
{
"event_id": 25,
"event_key": "level_10",
"name": "Reach Level 10",
"description": "Upgrade your kingdom to level 10",
"payout_usd": 0.7000,
"points": 70,
"completed": false
}
],
"click_tracking_url": "https://clipzaar.com/offerwall/click?appid=1&campaign_id=12&user_id=test_player_1"
}
]
}
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 |
{event_id} |
The unique event identifier setup in Grow (e.g. install, signup, level5). |
install |
{event_name} |
The display name of the completed event from the campaign. | Install App |
{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 PubAdzo V2's backend:
<?php
// 1. Retrieve query parameters sent by PubAdzo V2
$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'] : '';
$eventId = isset($_GET['event_id']) ? $_GET['event_id'] : ''; // e.g. install, signup, level5
$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 event $eventId of 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, payout, points, offer_id, offer_name, event_id, transaction_id, ip, 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 event_id on 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')
event_id = request.args.get('event_id') # e.g. install, signup, level5
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.