> For the complete documentation index, see [llms.txt](https://ctracker-docs.keywordrush.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://ctracker-docs.keywordrush.com/affiliate-networks/postbacks.md).

# Postbacks

A **postback** is the network calling your site the moment a sale happens, instead of your site asking later. It is how a [custom network](/affiliate-networks/custom-network.md) delivers orders.

{% hint style="info" %}
**Worth knowing before you start.** This is one of the more technical parts of Cashback Tracker.

You will be working from the network's own documentation to connect this up. None of it is hard, but the details matter: which field you point at what is what decides how much a member is paid.

**Send test postback** checks your work as often as you like, so you can get this right before any real orders arrive.

Built-in networks do not use postbacks and need none of this.
{% endhint %}

{% hint style="danger" %}
**Do not set a postback up for a network that already has a built-in module** — Admitad, AWIN, CJ, impact.com and the rest.

You would have to create a second, custom network to do it, and Cashback Tracker treats that as a different network entirely. The same purchase would arrive twice — once from the built-in network's own order download, once by postback — and be stored as two orders, **crediting the member twice**. The two copies would also use different cashback rates, so neither figure would be right.

If you want a built-in network to report sooner, change **Download new orders** in `Settings` → `General` instead. It goes down to every 15 minutes.
{% endhint %}

### Switching the endpoint on

`Settings` → `General` → **How orders reach your site** → **Postback endpoint**.

It is **off by default**, and it stays off unless you turn it on.

This is the one setting worth understanding rather than just ticking. Accepting a postback means letting a stranger's server call yours without logging in — that is what a postback is, and it is why every other control on this page exists. While this switch is off, that address does not exist on your site at all. Not protected, not refused: **absent**. There is nothing to find, nothing to probe, and nothing that could be reached by a mistake in a future version.

That is worth keeping off if you do not use a custom network, which is most sites.

If you have a custom network switched on and the endpoint switched off, the Networks screen tells you so, with a **Turn it on** button — orders sent to you in that state are simply lost.

{% hint style="info" %}
Turning the endpoint off does not affect built-in networks. They are polled over an authenticated API and never use this.
{% endhint %}

### Setting one up

`Networks` → your custom network → **Postback setup**.

The modal asks one question first, and everything else follows from it.

#### "I can choose the parameters"

Use this when the network lets you build the callback URL yourself — most do. You will see a list of their available placeholders in their dashboard, usually written like `[[[order_id]]]` or `{transaction_id}`.

Put each of their placeholders beside our field. Cashback Tracker builds the finished URL underneath as you type. Copy it, paste it into the network's dashboard, done.

Because we chose the parameter names, there is nothing else to map.

**GET or POST** — either works, pick whichever the network offers. Choosing POST gives you the endpoint on its own plus the parameters as a list, since POST forms usually ask for them as separate rows rather than as one URL.

#### "The network sends fixed parameters"

Use this when the network posts a set list of parameters you cannot rename. Fill in the name each value arrives under.

If the network sends **JSON**, and the value you want is nested inside another value, use a dot: `saleAmount.amount`.

![Postback settings: the endpoint, its token and the field mapping](https://3545740495-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-M69sU0hfZj3QsteYxmI%2Fuploads%2Fgit-blob-612f84b47d641e533e755d8302766887cca03316%2Fpostback-setup.webp?alt=media)

### The two fields that matter most

Everything else is optional. These two are not:

* **Order ID** — must be digits only, and unique per order at the network. Do not map the shop's own order reference here; those often contain letters, and two different shops can use the same number.
* **Member subid** — whatever carries the value we put on the tracking link. Without it an order arrives belonging to nobody, and is refused.

### Networks that send no status

Some networks fire a separate postback for each status — one when the order appears, another when it is approved, another when it is declined — and send no field saying which.

For those, leave **Status** empty and set up one URL per status in their dashboard, adding your own marker to the end of each:

```
…&status=approved
…&status=declined
```

### Testing before you trust it

**Send test postback** runs a sample through every check and shows you exactly what Cashback Tracker read from it.

It writes nothing. No order is created, nothing is credited, and the postback log is not touched — so you can press it as often as you like.

Two kinds of answer:

* **A failure** means the mapping is wrong, and the message says which field. Fix it and test again.
* **A note** in blue is about *this* request rather than about your mapping. If you use an IP allowlist, your own browser will not be on it — that is expected and correct, because a real postback comes from the network, not from you.

### Keeping it safe

The postback URL contains a token, and it lives in someone else's dashboard and in both sides' server logs. Treat it as something that can leak, and layer on whatever the network supports:

| Setting                                          | What it does                                                                                                    |
| ------------------------------------------------ | --------------------------------------------------------------------------------------------------------------- |
| **Allowed IPs**                                  | The strongest control here. If the network publishes its addresses, list them and nothing else can post at all. |
| **Signature parameter** and **Signature secret** | Real proof, where the network can sign its requests. See below — the two have to agree on exactly how.          |
| **Maximum amount**                               | Rejects any order above this, including a later revision. A forged million-pound sale goes nowhere.             |
| **Rate limit**                                   | Requests per minute. Caps how much damage a leaked token can do before you notice.                              |
| **New token**                                    | Regenerates it. The old URL stops working immediately, so update the network's dashboard straight after.        |

And if you ever need to stop everything at once: switching **Postback endpoint** off removes the address entirely, for every custom network, immediately. Switching a single network off under **Edit** does the same for just that one.

#### If the network can sign its requests

A signature is the only control here that proves who sent a postback, rather than just limiting the damage if the token leaks. It is worth using when the network supports it.

{% hint style="info" %}
**Most networks will not use the rule below.** A network signs however it signs, and it is not going to change that for your site. The rule here is the convention Cashback Tracker follows when nothing tells it otherwise — useful when you control both ends, such as your own shop or a system you wrote posting back to yourself.

When the network signs its own way, that is what the filter further down is for. It is the normal case, not the fallback.
{% endhint %}

Both sides have to agree on exactly what gets signed, and Cashback Tracker's rule is:

```
HMAC-SHA256( order id | subid | sale amount | commission amount | currency | status,
             your signature secret )
```

The six values are joined with a vertical bar between them, in that order, exactly as they arrive in the postback — nothing is reformatted, rounded or reordered. A field the network does not send is still there, as an empty value between two bars.

So an order `4242` for member subid `cbtrkr-7`, a sale of `10.00`, a commission of `2.00` in `USD` with status `approved`, is signed as the text:

```
4242|cbtrkr-7|10.00|2.00|USD|approved
```

and the same postback with no commission field mapped would be:

```
4242|cbtrkr-7|10.00||USD|approved
```

The result is compared as lowercase hexadecimal.

Two things about that rule are deliberate, and worth understanding if you are implementing the other side of it:

* **It covers every field that decides money.** The subid decides *who* is paid, the commission usually decides *how much*, and the status decides whether the money is released. A signature over the sale amount alone would leave all three free to be changed by anyone replaying a request.
* **The bar matters.** Joined with nothing between them, a sale of `25.00` on order `123` and a sale of `25.001` on order `23` are the same text, and so the same signature. The separator is what stops a value being shifted across the boundary into its neighbour.

Set **Signature secret** to the shared secret, and **Signature parameter** to the name of the parameter the signature arrives in. Leave both empty if the network cannot sign; the check is skipped.

#### When the network signs its own way

Which is most of the time. A different set of fields, a different order, a different separator, base64 instead of hex, SHA-1 instead of SHA-256 — none of that will match the rule above, and there is nothing on the Networks screen that changes it.

That is expected, and it is handled in code. A small snippet in your theme or a site plugin tells Cashback Tracker what signature to expect:

```php
add_filter( 'cbtrkr_postback_expected_signature', function ( $signature, $params, $network ) {
    // Return the signature this network would send, however it builds it.
    return hash_hmac( 'sha1', $params['order_id'] . $params['currency'], $network['signature_secret'] );
}, 10, 3 );
```

Cashback Tracker still does the comparison itself, so there is no way to get that part wrong.

If none of that is an option, use the IP allowlist instead — it is the stronger control anyway.

If your site is behind Cloudflare or a similar proxy, set **Trusted proxy header** in `Settings` → `General` as well — otherwise every request appears to come from the proxy and the allowed-IP list cannot work. Leave it empty if you are not behind one; setting it without a proxy in front would let anyone claim any address.

Prefer a header your proxy sets outright, such as `HTTP_CF_CONNECTING_IP`. If the header holds a list, as `HTTP_X_FORWARDED_FOR` does, Cashback Tracker reads the **last** address — the one your own proxy added. Everything before it came from the caller.

### Reading the log

**Log** on the network row shows every attempt, accepted or refused, with the reason. Tokens and signatures are stripped before anything is stored.

It is the first place to look when a member says an order never appeared. Kept for 30 days by default — `Settings` → `General` → **Keep postback log (days)**.

### Related

* [Building a custom network](/affiliate-networks/custom-network.md)
* [Importing orders](/affiliate-networks/importing-orders.md)
* [Logs and alerts](/running-your-site/logs-and-alerts.md)
