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
| Attack | What happens | What stops it |
|---|---|---|
| Forged postback | Someone who guessed or found your postback URL calls it with their own user ID and a high reward | Signature check and IP allowlist |
| Replayed postback | A real postback is sent again, crediting the same conversion twice | Unique transaction ID per network |
| Parameter tampering | The reward or user ID in a real request is edited before it is resent | Signature that covers every field you use |
| Conversion without a click | Credit arrives for a user who never opened that offer on your site | Match conversions to your own click log |
| Ignored reversals | The network reverses a conversion but your site never takes the balance back | Handle 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
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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_transactionflags a transaction ID that was already seen for the site.conversion_without_clickflags a conversion with no matching click from that account.click_conversion_country_mismatchflags a click from one country and a conversion from another.conversion_velocityand the offer speed checks flag offers completed faster than that offer normally takes.earnings_spikeflags 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.