PubAdzo.Docs
Back to Dashboard

Grow Advertiser Integration Guide

Learn how to track clicks, report installation events, and securely encrypt your postbacks to prevent conversion fraud.

Integration Overview

The Adzo Grow Tracking System allows advertisers to verify completions. The user flow is structured as follows:

  • 1. Click Redirection: User clicks an offer on the Reward Wall and is redirected through Adzo to your Store URL with a unique click_id query parameter.
  • 2. Client Engagement: The user installs your application or completes registration.
  • 3. Conversion Postback: Your application or server triggers our callback postback URL with the received click_id and your private security credentials.

1. Receiving Clicks

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.

Your Tracking URL Template
https://play.google.com/store/apps/details?id=com.app.name&referrer=click_id%3D{click_id}

2. Campaign Postback (Encryption & Security)

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.
Method A: Direct Token Verification
Pass the security token inside the request query parameters:
GET https://clipzaar.com/postback/callback?click_id={click_id}&event=install&security_token={CAMPAIGN_SECURITY_TOKEN}
Method B: Signature Verification (Recommended)
Calculate the MD5 signature locally to prevent exposing your token over public networks:
signature = md5(click_id + security_token)
Query string URL example:
GET https://clipzaar.com/postback/callback?click_id={click_id}&event=install&signature={signature}

Grow Hashing Mathematical Formula

The postback signature uses a secure MD5 hashing algorithm. The input components are concatenated as a raw string in the exact sequence shown below:

signature = MD5( click_id + security_token )
Implementation Examples
<?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}")

Postback Rate Limiting & Fraud Prevention

  • Unlimited High Throughput: Our postback endpoint has no rate limits or throttling filters enabled, ensuring that high volumes of sudden conversions are tracked and processed instantly without any request drops.
  • Cryptographic Click Fingerprinting: Every click session generates a unique 26-character identifier (click_id) that maps to the advertiser's campaign. Postbacks matching non-existent or fabricated click_ids are immediately rejected.
  • Double-Conversion Replay Block: A postback for a specific event step can be processed exactly once per click. Replay requests sending the same click_id and event parameters are intercepted, prevented from double-crediting balances, and logged as duplicate conversion attempts.

Offerwall Publisher Integration Guide

Learn how to fetch JSON feeds, bind reward widgets, and set up secure callback listeners with MD5/SHA256 HMAC signature verification.

Type A: Render Integration (Iframe / WebView)

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.

Integration URL Structure
URL https://clipzaar.com/offerwall/render?appid={your_offerwall_id}&user_id={your_unique_user_id}
HTML Iframe Example Code
HTML
<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>

Type B: JSON API Feed Integration (For Custom / Native UI)

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.

API Endpoint
GET https://clipzaar.com/api/v1/offerwall?appid={your_offerwall_id}&user_id={your_unique_user_id}
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:
  • game: Filter for game offers.
  • app: Filter for standard application installs.
  • one_step: Single conversion events.
  • multiple_step: Multi-milestone campaigns.
  • once: Non-repeatable campaigns.
  • repeat: Repeatable/multi-run campaigns.
  • ongoing: Campaigns user has click logs for but hasn't fully completed.
  • completed: Campaigns user has fully finished.
JSON Feed Output Example
JSON Response
[
  {
    "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
      }
    ]
  }
]

Publisher Callback Macro Tags

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

Postback Signature Verification Codes

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.

How Adzo Outgoing Postback URL is Compiled & Sent

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:

Your Configured Template URL:
https://yourdomain.com/adzo-listener?user={user_id}&payout={payout}&points={points}&offer_id={offer_id}&offer_name={offer_name}&transaction_id={transaction_id}&ip={ip}&sig={sig}
Actual GET Request Triggered by Adzo Backend:
GET https://yourdomain.com/adzo-listener?user=test_player_1&payout=1.2000&points=1200&offer_id=15&offer_name=Survey+Completion&transaction_id=01J234567890ABCDEFGHJKMNPQ&ip=192.168.1.50&sig=5af1ab0f9...
Method A: MD5 Hashing Formula
signature = MD5( user_id + payout_formatted + api_secret_key )
* Note: Payout must be formatted to exactly 4 decimal places with a dot separator (e.g. "1.2000").
Method B: HMAC SHA256 Hashing Formula
sig = HMAC-SHA256( user_id + ":" + payout_formatted, api_secret_key )
* Note: Payout must be formatted to exactly 4 decimal places with a dot separator (e.g. "1.2000").
PHP Endpoint Script
<?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"]);
}
?>
Node.js Express Handler
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' });
  }
});
Python Flask Route
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

Publisher Rate Limiting & Callback Best Practices

  • Support High Volume Throughput: During high-traffic periods, your endpoint may receive sudden bursts of conversion callbacks. Ensure that your receiver URL either does not rate limit Adzo server IP addresses or handles high-concurrency requests smoothly to avoid dropping authentic conversions.
  • Double-Spend Prevention: Always store and log unique transaction/conversion records on your database. If you receive a subsequent duplicate callback for an already rewarded completion, do not credit points again. Instead, respond immediately with a success message (e.g. {"status":"success"}) to prevent balance inflation while acknowledging our notification system.
  • IP Whitelisting (Optional): We highly recommend whitelisting the Adzo tracking server's IP address on your web server configurations or cloud firewalls (like Cloudflare) to ensure only authentic requests can call your postback handler.