turnley.dev

PHP and MySQL code for faucet operators

Tools & Calculators

ADL-002

Verify an AdsLab postback signature

It is sha256 of the transaction id, a hyphen and your Security Hash — not the Publisher API key their integration page names.

Everything paid on your site depends on this check, and the documentation points at the wrong key. The integration page says postbacks are signed with the Publisher API key. They are signed with the Security Hash, which is a different value in a different box of the same dashboard.

The formula is sha256(txid . '-' . securityHash), compared case-insensitively because the case you receive is not guaranteed.

Task postbacks carry a reward; ad postbacks (interstitial and rewarded) do not. Both are signed the same way, so one verifier covers all three placements — but each placement has its own id, so the endpoint also has to know which one it is for.

PHP
/**
 * Returns the verified postback, or null if it is not ours.
 * Task params: uid, pid, reward, vCurrency, txid (or trans_id), type, status, ip, country, name, signature
 * Ad params:   uid, pid, txid, signature   (no reward)
 */
function adslab_postback(string $securityHash, string $expectedPid): ?array
{
    $p = $_GET + $_POST;

    $txid = (string) ($p['txid'] ?? $p['trans_id'] ?? '');
    $sig  = (string) ($p['signature'] ?? '');
    $pid  = (string) ($p['pid'] ?? '');

    if ($txid === '' || $sig === '' || $securityHash === '') {
        return null;
    }
    // Documented as the Publisher API key. It is the Security Hash.
    $expected = hash('sha256', $txid . '-' . $securityHash);
    if (!hash_equals($expected, strtolower($sig))) {
        return null;
    }
    // Each placement signs with the same hash, so pin the placement as well.
    if ($expectedPid !== '' && !hash_equals($expectedPid, $pid)) {
        return null;
    }

    $status = strtolower((string) ($p['status'] ?? 'credit'));

    return [
        'txid'    => $txid,
        'pid'     => $pid,
        'uid'     => (string) ($p['uid'] ?? ''),
        'reward'  => (string) ($p['reward'] ?? '0'),   // decimal string, absent on ad postbacks
        'type'    => (string) ($p['type'] ?? ''),
        'country' => strtoupper((string) ($p['country'] ?? '')),
        'action'  => adslab_action($status),
    ];
}

/** Their status vocabulary is inconsistent across doc pages. */
function adslab_action(string $status): string
{
    return match ($status) {
        'chargeback', 'reversed', 'reverse' => 'reverse',
        'rejected', 'reject'                => 'reject',
        default                             => 'credit',
    };
}

Using it

Their own postback tester signs with a txid it does not include in the request, so the signature can never match from it. Only real traffic confirms the integration.

Log every postback with the raw query string before verifying. The log is what tells you whether a missing credit was never sent, failed the signature, or was deduped.

Keep one endpoint file per placement, each with its own placement id, rather than pointing all three postback URLs at one script.

What bites people

Compare with hash_equals and lowercase what arrived. A plain equality check on mixed case rejects valid postbacks.

An empty Security Hash makes every signature fail silently. Treat unconfigured as refuse, and show it in admin.

Do not pay from this function. Verify here, credit in a separate step that owns the deduplication.

This one touches real money. Point it at a throwaway wallet and watch a full cycle before you trust it with a live balance.

Also in Ad Networks and Offerwalls