CashoutGuard: fraud prevention for rewards, GPT, faucet, PTC and offerwall sites.

How to secure your offerwall postbacks against fake conversions

On a rewards site the postback is the moment money is created: a network calls your server and you credit a user. If anyone else can make that call, they can print balance. This guide shows how postbacks get abused and how to lock the endpoint down.

Short answer

Verify a signature on every postback, allow only the network's server IPs, make each transaction ID unique, match every conversion to a click in your own log and handle reversals. A correctly signed postback can still pay a fraudster, so also check the account when the conversion arrives.

Forged postback
Stopped by a signature check and an IP allowlist.
Replayed postback
Stopped by a unique transaction ID per network.
Parameter tampering
Stopped by a signature that covers every field you use.
Conversion without a click
Caught by matching conversions to your own click log.

How an offerwall postback works

When a user completes an offer, the offerwall network sends an HTTP request to a URL on your server, the postback (also called a callback or S2S notification). It carries the user ID you passed in the offer link, a transaction ID, the reward amount and usually a status and a signature. Your code reads it and credits the user.

That endpoint is public by design, because the network must reach it. Everything that protects it comes from your own checks. Many small GPT sites copy a sample script from the network documentation, test it once and never look at it again. That is exactly the endpoint attackers look for.

Most networks send the postback as a GET request with the values in the query string, a few use POST. Some retry the same postback several times until your server answers with the expected response, usually 1 or OK. Networks also differ in what they send when a conversion is reversed: some send a separate status field, others repeat the postback with a negative reward. Read the documentation of every network you use, because your checks have to handle each format, including the retries and reversals, and a script written for one network is rarely safe for another without changes.

How postbacks get abused

AttackWhat happensWhat stops it
Forged postbackSomeone who guessed or found your postback URL calls it with their own user ID and a high rewardSignature check and IP allowlist
Replayed postbackA real postback is sent again, crediting the same conversion twiceUnique transaction ID per network
Parameter tamperingThe reward or user ID in a real request is edited before it is resentSignature that covers every field you use
Conversion without a clickCredit arrives for a user who never opened that offer on your siteMatch conversions to your own click log
Ignored reversalsThe network reverses a conversion but your site never takes the balance backHandle the reversal status and negative amounts

Postback URLs leak more often than people expect: in browser history when a developer tests them, in public GitHub repositories, in support tickets and in screenshots shared on forums. Assume the URL is known and make the checks do the work.

The postback security checklist

  1. Verify the signature on every request. Most networks sign postbacks with a hash of the fields plus a secret key, for example an MD5 or SHA-256 of user ID, transaction ID, reward and secret. Recompute it and reject any request where it does not match. Compare with a constant-time function.
  2. Allow only the network's server IPs. Networks publish the IP addresses their postbacks come from. Reject everything else. Keep the list per network and review it when a network announces changes.
  3. Make the transaction ID unique. Store the network name plus transaction ID with a unique index in your database and credit only on the first insert. A replay then fails at the database, even if two copies arrive at the same moment.
  4. Never trust the amount alone. Check the reward against what that offer normally pays, and cap it. A postback worth 50 times the usual amount should wait for review.
  5. Match the conversion to a click. Log every offer click with the user and offer. A conversion with no matching click from that user is a strong fraud signal.
  6. Handle reversals. Networks send a reversal (often a status such as 2 or a negative amount) when they cancel a conversion. Take the balance back, and if it is already paid out, record the debt against the account.
  7. Keep the secret key out of your code. Put it in an environment variable, rotate it if it was ever committed, and use a different key per network.
  8. Log every postback, including rejected ones. The rejected ones tell you when someone is probing the endpoint.
// Minimal postback guard (PHP). Adapt the field names to your network.
$expected = hash('sha256', $_GET['user_id'].$_GET['trans_id'].$_GET['reward'].getenv('NETWORK_SECRET'));
if (! hash_equals($expected, (string) ($_GET['signature'] ?? ''))) {
    http_response_code(403);
    exit('bad signature');
}
if (! in_array($_SERVER['REMOTE_ADDR'], NETWORK_IPS, true)) {
    http_response_code(403);
    exit('bad ip');
}
// Unique index on (network, trans_id): a replay fails here.
$inserted = $db->insertIgnore('conversions', ['network' => 'example', 'trans_id' => $_GET['trans_id'], /* ... */]);
if (! $inserted) {
    exit('1'); // already credited, acknowledge so the network stops retrying
}

When the postback is real but the user is not

A perfectly signed postback can still pay a fraudster. The network confirms that an offer was completed, not that the person behind it is a real, single user. VPN farms, emulators and multi-accounts all produce genuine postbacks, and those are the conversions the network reverses weeks later.

That is why the postback is also the right moment to check the account: how many accounts share its device, whether the conversion country matches the click country, whether the offer was finished faster than a human could, and whether today's earnings are far above the account's normal day. Credit the balance, but hold it for review when those checks fail.

How CashoutGuard helps

CashoutGuard does not replace your signature and IP checks; those belong in your postback code. It adds the account-level checks that a signature cannot give you. Send a conversion event from your postback handler with the offer, transaction ID and payout, and a click event when a user opens an offer.

  • duplicate_transaction flags a transaction ID that was already seen for the site.
  • conversion_without_click flags a conversion with no matching click from that account.
  • click_conversion_country_mismatch flags a click from one country and a conversion from another.
  • conversion_velocity and the offer speed checks flag offers completed faster than that offer normally takes.
  • earnings_spike flags a day far above the account's usual earnings.

Each call returns allow, review or block with the reasons, so your postback handler can credit the user but keep the balance on hold until you look. See the docs for the event format and the cashout checklist for what to check before paying.

Frequently asked questions

Is an IP allowlist enough on its own?

No. Use it together with the signature. The allowlist blocks strangers, the signature proves the fields were not changed, and the unique transaction ID stops replays.

What should my endpoint return for a duplicate?

Most networks retry until they get a success response. Acknowledge duplicates with the success response your network expects, but do not credit again.

MD5 or SHA-256 for the signature?

Use what your network sends. Many networks still use MD5 of the fields plus a secret. It is weaker in general, but with a long secret key it still stops simple forgery. Prefer HMAC-SHA256 when the network offers it.

Should I pay users instantly after a postback?

Crediting instantly is good for users, but hold the first cashouts of new accounts and any unusually large conversions until they pass review. Networks can reverse conversions for weeks.

Keep reading