Conversions

What happens between an advertiser saying "this user did the thing" and a publisher being able to withdraw the money.

States

StateMeansMoney
pending Recorded and inside its hold period Advertiser already debited; publisher amount is pending, not withdrawable
approved The hold elapsed with no objection Publisher balance credited and withdrawable
reversed Withdrawn by the advertiser or by staff Advertiser refunded; publisher amount removed
rejected Never accepted — expired, capped, or fraud-scored out Nothing moved at all

The hold period

Each offer sets hold_hours, 72 by default. During it the advertiser can reverse the conversion and the publisher cannot withdraw the money. When it elapses the cron worker approves everything nobody touched.

The hold exists for one reason: without it a publisher can convert fraudulently, withdraw, and be gone before the advertiser has finished reconciling. It is the single most important control on the platform.

Deduplication

Two independent guards, so a retrying advertiser never pays twice:

  • A unique index on (offer_id, external_txn_id) — the primary guard, used whenever the advertiser sends a txn_id.
  • A unique index on (click_id, goal_key) — the fallback when they do not. Safer, but it means one click can only ever convert a given goal once.

A duplicate returns 200 DUPLICATE, never an error.

Why one was rejected

Every inbound call is logged in full — query string, body, IP and outcome — under Postbacks on the advertiser dashboard. The usual causes, in order of how often they actually happen:

OutcomeUsual cause
UNKNOWN_CLICK{click_id} truncated, re-encoded or dropped by a redirect chain
INVALID_SIGNATUREFields concatenated in a different order, or the secret was rotated
EXPIREDGenuinely late, or the offer's window is shorter than your attribution window
CAPPEDDaily cap, total cap, per-user cap, or the wallet cannot cover it
DUPLICATEWorking as intended — your retry logic is doing its job

Fraud scoring

Every conversion is scored before it is accepted. A score at or above the platform threshold is rejected outright; anything lower is recorded as a flag with a reason and left for review. The rules that run:

RuleCatches
Click-to-conversion floorConversions arriving seconds after the click
Velocity limitAn app suddenly producing far more conversions per hour than usual
Per-user capOne user farming one offer
Duplicate IPMany "different" users behind one address
BlocklistIPs, countries, devices and users already known to be bad

Reversals

An advertiser can reverse from their conversion list, or by calling the inbound endpoint with status=reversed and the original txn_id. Either way:

  • The advertiser's wallet is refunded in full.
  • The publisher's pending or approved amount is removed.
  • A status=reversed postback goes out so the publisher can deduct the coins they granted.
  • Both ledger rows are written — nothing is deleted, ever.
Reversing an approved conversion can push a publisher's balance negative if they have already withdrawn. That is allowed and visible; the negative balance is recovered from subsequent earnings.