Postback Setup

A postback is an HTTP request Preads sends to your server when a conversion is recorded, so your system can credit the end user without polling.

Read the two behaviour notes below before you build.
  • Postbacks fire for offerwall conversions only — not for Content Locker or Link Locker conversions. See Which conversions trigger a postback.
  • {payout} and {reward} do not mean the same thing for every network. See Macro semantics before you use them to credit users.

Flow

End user clicks an offer in the Preads wall
        |
        v
Preads records the click  (GET /api/track)
        |
        v
The offer network completes the conversion
        |
        v
The network calls Preads   (ANY /api/postback)
        |
        v
Preads verifies the click, calculates rewards,
credits your balance, writes the conversion row
        |
        v
Preads calls YOUR postback URL   (GET, once)

Your postback is a notification, not a confirmation. The conversion is recorded and your balance is credited before your postback is sent. If your endpoint is down, the conversion still counts — you simply miss the notification.

Pending conversions earn nothing. A conversion only credits your balance when its status is approved. While it is pending no credit is applied, and if it later becomes rejected the publisher balance is decremented.

Configuration

The postback URL is set per app, so each of your apps can notify a different endpoint.

  1. Open Publisher → Apps and select your app.
  2. Set Postback URL.
  3. Save. The URL is validated when you save it and re-checked immediately before each request is sent.

URL rules

  • Must be http:// or https://.
  • Must resolve to a public address. Loopback and private ranges (127.0.0.0/8, 10.0.0.0/8, 192.168.0.0/16, 169.254.0.0/16, and similar) are rejected. The literal host localhost is the one exception, so it works in development.
  • A blocked URL is not retried. The attempt is recorded as failed with the message "Blocked: postback URL is not a public http(s) address".
  • There is no placeholder for the app ID or the app's currency. If you need them, hardcode them into each app's URL, or use the {user_id} you already control.

Macro reference

Macros in your postback URL are replaced with conversion values. Place them anywhere in the query string.

Macro Value Currency
{user_id} The end user's ID — the userId you passed to the wall or to /api/track —
{transaction_id} Unique conversion ID. Use this as your idempotency key. —
{offer_id} The ID of the offer's network, not the offer. See the note below. —
{offer_name} Offer name, with the step name appended for multi-step offers —
{payout} Meaning depends on the network type. See Macro semantics. USD
{reward} Meaning depends on the network type. See Macro semantics. USD or app-local
{ip} IP address recorded on the original click —
{country} Two-letter country code detected on the click —
{device} Device type detected on the click —
{status} approved, rejected or pending —
{aff_sub} – {aff_sub4} Your own sub-parameters, echoed back from the click —
Macros that do not exist. {currency}, {website_id}, {payout_local}, {timestamp} and {app_id} are not supported. If you paste one into your URL it is sent to your server literally, braces included — it does not error and it does not expand.

Every macro except {status} is URL-encoded before substitution. All values are therefore safe to read directly from $_GET / request.query without further decoding.

Macro semantics — read before crediting users

{offer_id} is a network ID

{offer_id} resolves to the offer's network ID. It is not the offerId you passed to GET /api/track, and it is not unique per offer — every offer from the same network shares it. If you reconcile conversions against offers in your own reporting, use {offer_name} or the network ID knowingly; there is no macro that gives you the offer ID.

{payout} and {reward} vary by network type

These two macros read the same two database columns for every conversion, but the platform writes those columns differently depending on how the conversion arrived:

Conversion arrived via {payout} {reward}
Offerwall (API network) What the network paid, USD Your share, USD
Partner wall (iframe network) Your share, USD The end user's reward, in your app's currency
Practical consequence. If you credit end users from {reward}, the same postback URL will hand you a USD amount for one conversion and a local-currency amount for the next. Do not assume a single currency for this field.

There is no macro that returns the end user's reward consistently across both network types. If you need a reliable per-conversion breakdown, reconcile against the conversion you received (using {transaction_id}) rather than inferring it from the amount fields.

Example

A postback URL for a rewards endpoint:

https://api.example.com/rewards?uid={user_id}&txn={transaction_id}&offer={offer_name}&amount={reward}&status={status}&camp={aff_sub}

A request Preads sends for an approved conversion:

GET /rewards?uid=user_8812&txn=clk_68f2a1b0c3d4e5f6_a1b2c3d4e5f60718&offer=Complete+a+signup&amount=1.2500&status=approved&camp=summer_promo

Delivery behaviour

Property Value
Method GET
Timeout 10 seconds
Attempts One. No retries, no queue.
Signature None. The request carries no HMAC, token or shared secret.
Success Any 2xx status. Anything else is recorded as failed.
On failure The conversion is still counted and your balance is still credited.
Trigger Whenever the conversion is created and whenever its status is resolved — so the same conversion can notify you twice, once as pending and again as approved or rejected.
No signature means no authenticity check. Because outbound postbacks are unsigned, anyone who learns your postback URL can forge a callback to it. Do not credit large value on the strength of a single postback. Recommended mitigations, in order:
  1. Require {transaction_id} to match a conversion you already expect, using it as an idempotency key.
  2. Require {user_id} to match a user who exists in your system.
  3. Cap the credit per {user_id} per period, and reconcile totals against your dashboard rather than summing postbacks.
Your app's postback_secret is generated and displayed, but it is not used to sign anything. It cannot be used to verify a postback.

Verifying delivery

Every attempt is recorded. Open Publisher → Reports → Postbacks to see the resolved URL, the HTTP status code, the first 1000 characters of your response body, and the error message when one occurred. The page also reports total, successful, failed and success-rate figures.

Testing your postback

Each app has a test tool: Publisher → Apps → your app → Postbacks. It lets you send a test postback with a chosen user_id and amount, re-send a previous test, simulate a network rejection, view the fully substituted URL before sending, and delete tests.

Test amounts are not production amounts. In a test, {payout} is the amount you typed in and {reward} is that amount multiplied by your app's exchange rate. In real traffic {payout} is the network payout and {reward} is your USD share. Use the test tool to check delivery, parsing and logging — not to calibrate amounts.

Test conversions are stored separately and are excluded from every figure in your dashboard, so sending tests will not distort your reports. Test postbacks carry {status}=approved unless you explicitly reject them, and their {transaction_id} is prefixed TEST_ — reject those in your own handler.

Which conversions trigger a postback

Outbound postbacks are sent for offerwall conversions only. The behaviour differs by source:

Conversion source Sends your postback?
Offerwall — API network offer Yes
Partner wall — iframe network wall Yes
Content Locker No
Link Locker No

Locker conversions are recorded and appear in your reports and statistics, but they are written with no app association and no outbound postback is ever sent for them. If you run a locker, do not build a crediting flow on postbacks — use the locker's statistics page or poll its progress endpoint instead.

Inbound: networks → Preads

For completeness: the endpoint offer networks post to is https://preads.hansaldev.com/api/postback (any HTTP method, rate-limited to 300 requests/minute per IP). It is unauthenticated by design, because third-party networks cannot be expected to sign requests.

It resolves the click ID from any of these parameters:

click_id, subId, subid, aff_sub, aff_sub1, s2, transaction_id, txn_id, tid

Responses:

Response HTTP Meaning
OK 200 Conversion recorded
1 200 Duplicate — already recorded for this click and status
0 400 No click ID could be resolved from the request
0 404 Click ID not found
Error 500 Processing failed

Any payout, amount or reward figure a network sends is ignored. The platform uses its own offer record, so a network cannot credit itself.

Troubleshooting

Symptom Cause and fix
No postback ever arrives Check Postbacks in the app first — the log shows the resolved URL and the exact error. Common causes: postback_url is empty (Preads returns early with "No postback URL configured" and sends nothing); the URL points at a private or loopback address and was blocked by the outbound guard; or the conversion came from a Content or Link Locker, which never post back.
Log shows "Blocked: postback URL is not a public http(s) address" The host resolves to a private, loopback or link-local address. Use a publicly reachable HTTPS endpoint. localhost is allowed for development only.
Log shows a timeout Your endpoint did not respond within 10 seconds. It is not retried — trigger a re-send from the test tool.
Log shows a non-2xx status Your endpoint responded, but not with 2xx. Any 2xx counts as success. Redirects are followed and the final status is recorded.
{user_id} is empty in your logs No userId was supplied when the wall was embedded or the click was tracked. See Offerwall Iframe.
You receive the same conversion more than once Expected. A conversion notifies once when created and again when its status resolves. Deduplicate on {transaction_id}.
{offer_id} is the same for different offers Correct behaviour — it is the network ID. Use {offer_name} instead.
{currency} arrived as literal text There is no such macro. It is passed through verbatim.

Related documentation

  • Offerwall Iframe — supplying userId and the aff_sub sub-parameters that come back here.
  • App/Website Setup — where the postback URL is set.
  • API Access — the API-key authentication used by the tracking endpoint.