turnley.dev

PHP and MySQL code for faucet operators

Tools & Calculators

FRD-004

Only count an impression a real browser actually rendered

Crediting on the ad request pays anything that can run curl. A signed nonce plus a beacon that only fires in a visible browser closes it.

If your ad endpoint credits the publisher when it is requested, then a loop that fetches that URL earns money. No browser, no rendering, no visitor. Everything downstream — your fraud stats, your advertiser reports — is measuring a script.

Splitting the request from the credit fixes it. The ad endpoint renders the creative and hands back a nonce, signed with a server key and bound to the requesting address and user agent. A small script in the creative fires a beacon after the frame has been visible for a moment, and only that beacon credits. Something that merely fetched the ad never runs the script and never earns.

The nonce has to be unforgeable, short lived, and single use. Sign it rather than storing it and you avoid a table write on every impression.

PHP
function issue_nonce(int $creativeId, string $visitorKey): string
{
    $payload = $creativeId . '|' . $visitorKey . '|' . time();
    $sig = hash_hmac('sha256', $payload, HMAC_SECRET);
    return base64_encode($payload . '|' . $sig);
}

function check_nonce(string $nonce, string $visitorKey, int $maxAgeSeconds = 120): ?int
{
    $raw = base64_decode($nonce, true);
    if ($raw === false) {
        return null;
    }
    $parts = explode('|', $raw);
    if (count($parts) !== 4) {
        return null;
    }
    [$creativeId, $boundKey, $issuedAt, $sig] = $parts;

    $expected = hash_hmac('sha256', $creativeId . '|' . $boundKey . '|' . $issuedAt, HMAC_SECRET);
    if (!hash_equals($expected, $sig)) {
        return null;                                   // forged
    }
    if (!hash_equals($boundKey, $visitorKey)) {
        return null;                                   // replayed from another machine
    }
    if (time() - (int) $issuedAt > $maxAgeSeconds) {
        return null;                                   // stale
    }
    return (int) $creativeId;
}

Using it

The beacon belongs in the creative, on a short delay, and gated on the frame being visible. document.visibilityState and a one second wait removes most prefetch and background-tab noise.

Keep a switch that reverts to crediting on request. On a network with no fraud pressure the extra call is not worth it, and you want that decision to be a setting rather than a redeploy.

What bites people

Binding to the address plus user agent means a visitor whose address changes mid-page — mobile handover, some VPNs — loses the impression. That is the right trade, but expect a small permanent gap between requests and credits and do not chase it as a bug.

Generate the signing secret on first use and store it outside the web root. A predictable secret makes the whole scheme decorative.

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 Anti-Bot and Fraud