turnley.dev

PHP and MySQL code for faucet operators

Tools & Calculators

ADL-003

Credit an AdsLab postback once, and handle reversals

Dedupe on the transaction id, answer 200 to everything, and treat a reversal as the full chain — user credit and referral commission both.

Ad networks retry anything that is not a 200, and they retry quickly. An endpoint that returns 500 on an unknown user, or 404 for a bad path, turns one event into a storm that also inflates your own counts.

So the rule is: always answer 200 with a short plain-text body, and decide inside whether the event does anything. A duplicate transaction id does nothing and still answers 200, which is how the retries stop.

Reversals are the half most sites skip. A chargeback has to take back what the completion paid, including any referral commission it generated, or your ledger drifts upward with every fraudulent conversion.

PHP
// adslab_postback.php
require __DIR__ . '/includes/bootstrap.php';
header('Content-Type: text/plain');

$pb = adslab_postback(setting('adslab_security_hash'), setting('adslab_task_pid'));
if ($pb === null) {
    echo 'bad';                                   // 200 even so: they retry anything else
    exit;
}

// UNIQUE KEY (txid, action) — a retry inserts nothing and credits nothing.
try {
    q('INSERT INTO adslab_events (txid, action, uid, reward, type, created_at) VALUES (?, ?, ?, ?, ?, NOW())',
      [$pb['txid'], $pb['action'], $pb['uid'], $pb['reward'], $pb['type']]);
} catch (PDOException $e) {
    echo 'ok';                                    // already seen
    exit;
}

$userId = (int) $pb['uid'];
$units  = (int) round(((float) $pb['reward']) * 100000000);   // integers past this point

switch ($pb['action']) {
    case 'credit':
        credit_user($userId, $units, 'adslab:' . $pb['type'], $pb['txid']);
        pay_referral_commission($userId, $units, $pb['txid']);
        break;

    case 'reverse':
        // The whole chain, not just the user's share.
        reverse_user_credit($userId, $units, $pb['txid']);
        reverse_referral_commission($userId, $pb['txid']);
        break;

    case 'reject':
        // Nothing was ever credited; record it so support can answer "why not paid".
        break;
}

echo 'ok';

Using it

Put the unique key on (txid, action), not txid alone — a credit and a later reversal share the transaction id and both need to land.

Show rejects to the user as "not accepted by the advertiser" somewhere. Silence on a task they completed is the single most common support message on an offerwall site.

Hold large completions for a period before they can be withdrawn. Reversals arrive days later, and money already paid out cannot be reversed.

What bites people

Never return a non-200 to signal your own failure. Log it and answer ok, or you will be rate limited by your own error handler.

A reversal on a user whose balance has already gone leaves a negative. Decide in advance whether that becomes a debt or is written off.

Round the decimal reward to integer units once, at the edge. Keeping it as a float through the ledger is how cents go missing.

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