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

Offerwall postback parameters: Lootably, AdGate Media, CPX Research and BitLabs

Every offerwall calls your server when a user completes an offer, and every offerwall names the same things differently. The user is {userID} on one, {s1} on another and [%USER:UID%] on a third. This guide puts the postback parameters of four offerwalls that rewards and GPT sites use most side by side, with how each one tells you about a reversal and how to check that a postback is genuine.

Short answer

Lootably sends {userID}, {transactionID}, {currencyReward}, {revenue}, {ip} and {status}; AdGate Media uses {s1} for the user, {points}, {payout} and {state}; CPX Research sends {user_id}, {trans_id}, {amount_local}, {amount_usd} and {status}; BitLabs uses [%USER:UID%], [%TX%], [%VALUE:CURRENCY%] and [%VALUE:USD%]. Credit each transaction once, verify the hash, and handle reversals.

Reversal flags
Lootably: status 0. AdGate: state "rejected". CPX Research: status 2. BitLabs: type RECONCILIATION.
Hashes
Lootably: SHA-256. BitLabs: HMAC SHA-1 of the full URL. CPX Research: a secure hash macro. AdGate: none, an IP whitelist.
Same idea, four names
The user ID is {userID}, {s1}, {user_id} or [%USER:UID%].
Checked against
Each offerwall's own postback documentation, October 2026.

Why the names matter

A postback is a request from the offerwall's server to yours. It says that a user completed an offer and how much to credit. Your endpoint reads a few values from it: who the user is, which transaction this is, how many points to give, and sometimes the IP the offer was started from. If one of them is read from the wrong parameter, the user is credited wrongly, twice, or never.

Each offerwall lets you build the postback URL yourself: you choose your parameter names and put the offerwall's macros as values, for example ?user={userID}&tx={transactionID}. The tables below list the macros. Your parameter names can be anything.

Lootably

MacroWhat it carries
{userID}The user who completed the offer
{transactionID}This conversion's ID at Lootably
{currencyReward}The reward in your currency, from your placement settings
{revenue}What you earned, in USD
{ip}The user's IP when they started the offer
{offerID} / {offerName}The offer
{goalID} / {goalName}The goal, for multi-step offers
{status}1 for a completion, 0 for a chargeback
{hash}SHA-256 signature

The hash is sha256 of userID, ip, revenue and currencyReward followed by your postback secret, concatenated in that order. Lootably documents status 0 for chargebacks but notes that, for now, postbacks are only sent with status 1, so keep your reversal handling ready without relying on it yet.

AdGate Media

MacroWhat it carries
{s1}Your user ID (up to 255 characters)
{conversion_id}The conversion ID
{points}Points to credit, decimal or integer
{payout}What you earned, in USD
{offer_id} / {vc_title}The offer ID and the title shown on the wall
{session_ip}The IP of the user who completed the offer
{state}approved, rejected or pending
{status}Deprecated: AdGate now asks for {state} instead

AdGate's own example is ?conversion_id={conversion_id}&user_id={s1}&point_value={points}&usd_value={payout}&offer_title={vc_title}. Add {state}: without it AdGate only sends approved conversions; with it, a reversed conversion arrives again with state rejected and the same conversion ID, so you can find the original credit and take it back.

AdGate does not sign its postbacks. It publishes the IP addresses they come from and asks you to use a postback URL that only AdGate calls, so allow only those addresses on that endpoint.

CPX Research

MacroWhat it carries
{user_id}Your user ID
{trans_id}The transaction ID
{amount_local}The reward in your currency
{amount_usd}What you earned, in USD
{offer_ID}The survey or offer
{ip_click}The user's IP at the click
{subid} / {subid_2}Your own tracking values
{status}1 completed, 2 canceled
{secure_hash}Signature to verify the postback

A typical CPX postback looks like ?status={status}&trans_id={trans_id}&user_id={user_id}&amount_local={amount_local}&amount_usd={amount_usd}&hash={secure_hash}&ip_click={ip_click}. Note that a cancellation is status 2, not 0: code written for another offerwall that only checks for 0 will credit CPX cancellations as new completions.

BitLabs

MacroWhat it carries
[%USER:UID%]Your user ID (up to 255 characters)
[%TX%]The transaction ID, to avoid double booking
[%VALUE:CURRENCY%]The reward in your app currency
[%VALUE:USD%]What you earned, in USD
[%USER:IP_ADDRESS%]The user's IP address
[%OFFER:ID%] / [%OFFER:NAME%]The offer
[%ACTIVITY:TYPE%]COMPLETE, SCREENOUT, RECONCILIATION or START_BONUS
[%REF%]On a reconciliation, the original transaction it takes back

Older integrations use the short forms [%UID%], [%VAL%] and [%RAW%]; BitLabs' current documentation uses the names above. A reversal arrives as a callback of type RECONCILIATION: add [%REF%] to find the reward it reverses.

BitLabs attaches a hash to every callback: an HMAC SHA-1 of the complete URL, hex encoded, keyed with your app secret. Because it covers the whole URL, verify it before you parse or change anything, and use the URL exactly as it arrived.

What every postback endpoint should do

  1. Verify the signature with the method of that offerwall, and refuse the request if it does not match.
  2. Credit each transaction ID once. Offerwalls retry when your server is slow; a unique index on the transaction ID makes a retry harmless.
  3. Handle reversals. Know which value means a reversal for each offerwall (status 0 on Lootably, state rejected on AdGate, status 2 on CPX Research, type RECONCILIATION on BitLabs) and take the reward back, or hold it if it has not been paid out yet.
  4. Answer quickly with the response the offerwall expects, and do slow work after answering.
  5. Check the person, not only the request. A correctly signed postback can still pay a farm: ten accounts on one phone, a VPN to look American, an offer finished faster than anyone could play it.

The last point is the one a signature cannot cover. See how to secure offerwall postbacks for the full checklist and fake conversions from residential proxies for what signed fraud looks like.

Test your endpoint before going live

Four requests tell you whether an endpoint is ready. Send them yourself with curl or a browser, using a test user and made-up values:

  1. A normal completion. The test user is credited once, with the reward in your currency.
  2. The same request again. Nothing changes: the transaction ID is already known, and your endpoint still answers with success so the offerwall stops retrying.
  3. A reversal with the status value of that offerwall. The reward is taken back or held, and the account history shows why.
  4. A wrong hash. The request is refused and nothing is credited.

If any of the four behaves differently, fix it before the offerwall goes live: each one is a way real money leaks.

Checking every postback without code

CashoutGuard's postback relay sits between the offerwall and your endpoint. You paste your current postback URL, it gives you a new one to put in the offerwall, and every conversion is scored for VPNs, emulators, cloud phones and linked accounts before it reaches you. It reads the macros above even when your parameters have short names like ?a={userID}&b={transactionID}, and it learns from reversals sent with a status value, so chargebacks make the next decision better.

Frequently asked questions

Which macro should I use as the transaction ID?

The one each offerwall documents as unique per conversion: {transactionID} on Lootably, {conversion_id} on AdGate Media, {trans_id} on CPX Research and [%TX%] on BitLabs. Store it with a unique index.

Do all offerwalls send reversals?

Not all, and not the same way. AdGate resends with state rejected when {state} is in the URL, CPX Research uses status 2, BitLabs sends a RECONCILIATION callback, and Lootably documents status 0 but currently only sends completions. Where none arrives, reversals show up later in the offerwall's reports.

Should I credit the reward in USD or in my currency?

Credit the user in your currency ({currencyReward}, {points}, {amount_local}, [%VALUE:CURRENCY%]) and keep the USD value ({revenue}, {payout}, {amount_usd}, [%VALUE:USD%]) for your own accounting and fraud reports.

Keep reading