Postbacks
Get the sale in your tracker or on your server as it happens – and again on approval, rejection and refund. With macros, a signature, retries and a log.
A postback is a URL in your tracker (e.g. Voluum, RedTrack, Binom or Keitaro) or on your own server that Affilyflow calls when something happens to a sale on your links. Your tracker can then optimise campaigns on real sales – and you see the same as in the dashboard without fetching anything.
- The sale is sent when it comes in – and again when it is approved, rejected or refunded.
- Every call is signed, and a call without a 2xx response is retried for up to 24 hours.
- Every call is in a log with status code and response, and you can resend it.
- We never send the customer's data, the order amount or the store's full order number – only the last characters.
Create a postback
- Go to AI og API (AI and API) in the affiliate dashboard and find Postbacks.
- Press Ny postback (New postback) and paste the URL from your tracker with macros (see below).
- Choose GET (what most trackers use) or POST med JSON (POST with JSON), and choose the events.
- Copy the secret if your own server is going to check the signature. It is shown only once.
- Press Send test and check that your tracker received it.
You can have up to 5 postbacks. The URL must start with https:// or http:// and may only use port 443, 80, 8080 or 8443.
Your tracker's click ID
Your tracker has its own click ID. Pass it in the Affilyflow link as a sub-ID, and it comes back in the postback:
https://app.affilyflow.com/go/YOUR_LINK?sid={clickid}{clickid} is your tracker's own macro (its name differs from tracker to tracker). In the postback you then use {sub_id}:
https://your-tracker.com/postback?cid={sub_id}&payout={commission}¤cy={currency}&status={status}&txid={sale_id}The names of your tracker's parameters (cid, payout, txid …) are in the tracker's own guide. You have five sub-IDs (sid, sid2 … sid5) – see Links and sub-IDs.
Macros
| Macro | Content |
|---|---|
{click_id} | Affilyflow's click ID (c_…). |
{sub_id} | Sub-ID 1 from the link (?sid=). |
{sub_id2} … {sub_id5} | Sub-IDs 2–5 (?sid2= … ?sid5=). {sub2} … {sub5} work too. |
{commission} | Your commission after the event, with a full stop and two decimals (75.92). After a partial refund: the commission after the refund. For a rejected sale 0.00. |
{currency} | The currency of the commission (e.g. DKK). |
{status} | pending, approved or rejected. |
{event} | The event, e.g. sale.approved. |
{order_ref_masked} | The last characters of the store's order number (e.g. ••82) – to talk to the store about the same sale. |
{sale_id} | The sale's ID at Affilyflow – the same for every event on the sale. |
{event_id} | The event's ID (evt_…) – the same if the call is retried. |
{program_id} | The programme's ID. |
Every value is URL-encoded. An empty field stays empty (e.g. {sub_id3} when the link has no third sub-ID). Other macros are rejected when you save.
Events
| Event | Sent when |
|---|---|
sale.created | The store reports a sale on your link (pending approval). |
sale.approved | The sale is approved and counts towards your balance. |
sale.rejected | The sale is rejected – e.g. returned, cancelled or breaking the programme rules. |
sale.refunded | The sale is fully or partly refunded. {commission} is the commission after the refund. |
sale.updated | The commission changed in another way, e.g. a refund that is reversed. |
click.created | Every click on your links, about 2 minutes after the click. Optional – many calls. A click we filter out as a bot before then is not sent. |
A sale that is rejected as soon as it is recorded (e.g. for breaking the programme rules) arrives as sale.created with {status} = rejected – always check {status}. A sale through a personal discount code has no click, so the click macros are empty.
GET or POST
- GET: the URL is called with the macros filled in. This is what trackers expect.
- POST with JSON: the macros in the URL are filled in too, and the body is the whole event:
{
"id": "evt_6f1c2d0e8b9a4c3d9e7f1a2b3c4d5e6f",
"type": "sale.approved",
"created_at": "2026-11-12T10:14:03.512+00:00",
"test": false,
"data": {
"sale": {
"id": "0b6b2d8e-3f1a-4c55-9d2e-7a1b2c3d4e5f",
"order_ref_masked": "••82",
"status": "approved",
"previous_status": "pending",
"commission": 75.92,
"currency": "DKK",
"created_at": "2026-10-13T18:22:41.087+00:00",
"approved_at": "2026-11-12T10:14:03.512+00:00",
"approval_due_at": "2026-11-12T18:22:41.087+00:00",
"rejection_reason": null,
"attribution": "click",
"recurring": false,
"refund": null
},
"click": {
"click_id": "c_8Kq2mXo1Tz9w",
"clicked_at": "2026-10-13T17:58:02.114+00:00",
"link_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"sub_id": "fb-kampagne-12",
"sub_id2": "annonce-3",
"sub_id3": null,
"sub_id4": null,
"sub_id5": null,
"landing_path": "/produkter/uldplaid",
"device": "mobile",
"country": "DK"
},
"program": { "id": "5d1e2f3a-4b5c-4d6e-8f70-819a2b3c4d5e", "store": "Nordlys Bolig" }
}
}On a refund, refund holds type (partial or full), refunded_ratio, commission_before_refund and refunded_at. For a rejected sale, commission shows the commission the sale had – it is not paid out (the {commission} macro is 0.00). The store's rejection reasons in rejection_reason are written in Danish.
The signature
Every call has the header Affilyflow-Signature: t=…,v1=…. v1 is an HMAC-SHA256 with your secret over t, a full stop and:
- for POST: the raw body,
- for GET: the whole URL as it was called (with the macros filled in).
Trackers ignore the header, and that is fine. On your own server you should check it and reject calls where t is more than 5 minutes from your clock. Right after a new secret there can be two v1= (new and old) – accept the call if either matches. Code examples in Node.js and PHP are under Webhooks – it is the same signature.
Retries, pausing and the log
- Respond with a 2xx status within 10 seconds. Anything else is retried after about 1, 5, 15 and 30 minutes and then after 1, 2, 4, 6 and 8 hours – for up to 24 hours.
- After 20 failed attempts in a row the postback is paused, and the dashboard says so. Fix the problem, send a test and press Slå til (Turn on). Calls from the last 24 hours are then sent.
- Log shows the latest 20 calls: event, order, status, status code, attempts and the first 500 characters of the response. Send igen (Resend) sends a call again. The log is kept for 30 days.
- Send test sends example data (
c_test0000000000, sub-IDtest, commission39.92) and shows the response straight away. The POST body has"test": true. - The same event can arrive more than once. Use
{event_id}(or{sale_id}+{status}) to ignore duplicates.
Postbacks or the API?
Postbacks come to you when something happens. To fetch sales, clicks and numbers yourself, use the REST API or the MCP server.
Missing something?
Write to us – we answer quickly, and the page gets fixed.