DocsIntegration
Postbacks
One URL on your server receives every event: credits, pending results, rejections, and reversals. Here is what we send and how to handle it.
Setup
Add your postback URL in your dashboard under Apps, your app, Postback. We call it with a GET request for every event of every transaction, and we fill in the macros you put in the URL:
https://yoursite.com/postback?user={user_id}&tx={transaction_id}&event={event_id}&status={status}&reward={reward}&payout={payout}&hash={hash}
Use the macros you need, in any order and with any parameter names. Values are URL-encoded.
Macros
| Macro | Description |
|---|---|
{user_id} | The user ID you put in the offerwall link. |
{transaction_id} | Stays the same for the whole life of a transaction: pending, credited, rejected, and reversed events all carry the same value. |
{event_id} | Unique for every event we send. Useful for logging. |
{status} | What happened, as a number: 1, 2, 3, or 4. See Statuses. |
{state} | The same as {status} in words: credited, reversed, pending, or rejected. |
{reward} | The reward in your currency, after your conversion rate, user split, and rounding. Never negative. |
{currency} | The name of your currency, for example Coins. |
{payout} | What you earn for the transaction, in US dollars. Never negative. |
{offer_id} | The ID of the offer. |
{offer_name} | The name of the offer as your user saw it. |
{offer_type} | click, task, or offer. See Offer lifecycle. Every type uses the same statuses. |
{reason} | For rejections and reversals, a short reason. Empty otherwise. |
{country} | Two-letter country code of the user. |
{device} | desktop, android, or ios. |
{ip} | The IP address the user completed the offer from. |
{hash} | Signature to check that the call comes from us. See Verify the hash. |
Statuses
| Status | State | What to do |
|---|---|---|
1 | credited | Add {reward} to the user. |
2 | reversed | Take {reward} back from the user, if you credited this transaction before. |
3 | pending | Do not credit. Show the reward as pending. A status 1 or 4 follows later. Only sent if you turned pending events on in the Postback tab. |
4 | rejected | Do not credit. {reward} and {payout} are 0. |
Rules that keep your balances right:
- Credit only on status 1, and only once per
transaction_id. - A reversal (status 2) always carries the
transaction_idof the credit it cancels. If you never credited that transaction, ignore it. - Amounts are never negative. The status tells you whether to add or take back.
- The same event can arrive twice, for example after a retry. Store
transaction_idwith the last status you processed and skip repeats.
Verify the hash
{hash} is the HMAC-SHA256, as lowercase hex, of the transaction ID, user ID, reward, and status joined with colons, signed with your postback secret key from the Integration tab. Use the values exactly as they arrive.
$expected = hash_hmac('sha256', $tx . ':' . $userId . ':' . $reward . ':' . $status, $postbackSecretKey);
if (!hash_equals($expected, $hash)) {
http_response_code(403);
exit;
}
The postback secret key is different from the link hash salt. If you also want to allow-list IP addresses, ask [email protected] for the addresses we send from.
Responses and retries
- Answer with any 2xx status code within 6 seconds. We do not read the response body and we do not follow redirects.
- If your server fails or times out, we try again up to the number of retries you chose in the Postback tab, after about 1 minute, 5 minutes, 30 minutes, 2 hours, and 6 hours.
- Events of one
transaction_idarrive in order. A reversal is never sent before the credit it cancels, and a pending event that is still waiting for a retry is dropped once the final status is known. - Every attempt, with the status code your server returned, is listed in your dashboard under Logs, Postbacks.
- The Send test postback button sends status 1 for
test_userwith a $1 payout, so you can check your handler before going live.
Example handler
$tx = $_GET['tx'] ?? '';
$userId = $_GET['user'] ?? '';
$reward = $_GET['reward'] ?? '0';
$status = (int)($_GET['status'] ?? 0);
$expected = hash_hmac('sha256', $tx . ':' . $userId . ':' . $reward . ':' . $status, $postbackSecretKey);
if (!hash_equals($expected, $_GET['hash'] ?? '')) {
http_response_code(403);
exit;
}
$last = get_last_status($tx);
if ($status === 1 && $last !== 1 && $last !== 2) {
add_to_balance($userId, $reward);
save_status($tx, 1);
} elseif ($status === 2 && $last === 1) {
subtract_from_balance($userId, $reward);
save_status($tx, 2);
} elseif ($status === 3 && $last === null) {
save_status($tx, 3);
} elseif ($status === 4 && ($last === null || $last === 3)) {
save_status($tx, 4);
}
http_response_code(200);
