Postbacks
Two server-to-server calls, in opposite directions. Advertisers tell us a conversion happened; we tell publishers to credit their user.
Inbound — advertiser to Trovewall
Your endpoint is on your advertiser dashboard and looks like this:
GET https://trovewall.com/postback/in/adv_7f3c…?click_id={click_id}
&txn_id=YOUR_TXN&goal=install&payout=4.20&sig=…
| Parameter | Required | Notes |
|---|---|---|
click_id | Yes | Exactly the value we put in your tracking URL |
sig | Yes | HMAC-SHA256 (see below) unless signatures are disabled on your account |
txn_id | Strongly | Your identifier for the event. It is what makes retries safe. |
goal | No | Goal key for a multi-event offer. Defaults to default. |
payout | No | Override the offer price for this one conversion, in major units. Used for revenue-share offers. |
test | No | 1 records everything but bills nobody. |
Signing
HMAC-SHA256 over click_id + txn_id + goal concatenated, keyed with your postback secret.
$sig = hash_hmac('sha256', $clickId . $txnId . $goal, $postbackSecret);
Responses
| Body | Status | Meaning |
|---|---|---|
OK | 200 | Recorded. Money has moved. |
DUPLICATE | 200 | Already had this one. Nothing changed. Safe. |
UNKNOWN_CLICK | 404 | No such click. Usually a truncated or re-encoded {click_id}. |
EXPIRED | 410 | Outside the offer's conversion window. Logged, not billed. |
INVALID_SIGNATURE | 401 | Wrong secret, or the fields were concatenated in another order. |
CAPPED | 200 | Cap or budget reached. Deliberately not an error, so you stop retrying. |
(offer, txn_id), so the same call a hundred times bills once. If you
cannot send a txn_id we fall back to one conversion per click and
goal, which is safe but stops you legitimately converting the same goal twice.
Outbound — Trovewall to publisher
Set a postback URL per app. We call it when a conversion is created, and again
with status=reversed if one is later reversed.
https://you.example/trovewall?user_id={user_id}&amount={amount}
&trans_id={trans_id}&offer_id={offer_id}&status={status}&sig={signature}
Macros
| Macro | Value |
|---|---|
{user_id} | The user_id you passed into the wall |
{amount} | Virtual currency to credit, already converted with your exchange rate |
{payout} | Your cash earning, decimal major units |
{payout_cents} | The same, as an integer |
{trans_id} | Our conversion id — use it for your own idempotency |
{offer_id} · {offer_name} | Which offer converted |
{goal} | Goal key, or default |
{status} | approved or reversed |
{country} · {ip} | Where the click came from |
{sub1} … {sub5} | Whatever you attached to the click |
{timestamp} | Unix seconds when we sent it |
{signature} | HMAC-SHA256 of user_id + amount + trans_id, keyed with your app's postback secret |
Verifying us
$expected = hash_hmac(
'sha256',
$_GET['user_id'] . $_GET['amount'] . $_GET['trans_id'],
$appPostbackSecret
);
if (!hash_equals($expected, $_GET['sig'])) {
http_response_code(403);
exit('bad signature');
}
// Idempotency is yours to keep: store trans_id and ignore repeats.
credit_user($_GET['user_id'], (int) $_GET['amount'], $_GET['trans_id']);
echo 'OK';
What we expect back
Any 2xx. The body is logged but not parsed. Anything else — or no
response within 15 seconds — counts as a failure and we retry.
Retry schedule
Up to eight attempts with exponential backoff: 1 min, 2, 4, 8, 16, 32, 64, 128 — a little over four hours in total.
Open any row in Postback log to see the delivery in full: the exact URL we called, the body we sent, and every attempt with its status code, its duration and the response your server returned. Once your endpoint is healthy again, replay it from the same page — no need to write to us.
status=reversed with the same trans_id as the original.
Deduct the amount you credited — do not treat it as a new reward.