DEV Community

Prabhash Jha
Prabhash Jha

Posted on • Originally published at prabhashjha.com

Affiliate Tracking Breaks Quietly, Catch It Before Payout Day

Most tracking problems don't announce themselves. Nothing errors. No alert fires. The campaign runs, the dashboard fills with clicks, and everything looks normal. Right up until the end of the month, when the network's conversion count and yours disagree and somebody has to decide who's right.

By then you're negotiating about the past. That's the worst position to be in. Neither side can prove anything, and the money's already been spent.

The fix isn't a better tracker. It's checking one thing before launch instead of after payout.

Everything hinges on one value surviving the journey

Strip away the vocabulary (postback, S2S, pixel, macro, subid) and affiliate tracking is a single idea. If none of it is familiar yet, how to track affiliate sales as a complete beginner is the version before this one.

You attach a unique ID to a click. Later, the advertiser hands that same ID back to you attached to a conversion. Match the two, and you know which click earned the money.

That's the whole mechanism. Which means there's really only one failure. The ID does not survive the round trip. Everything else is a variation on that.

When the ID is missing, the conversion still happens. The customer still buys. The advertiser still books the revenue. The only thing that disappears is your ability to prove the sale was yours. And unprovable sales don't get paid. That makes broken tracking one of the mistakes that quietly kill beginners, because nothing on the dashboard looks wrong.

Network switches and patch cables in a data centre. Every hop between the click and the conversion is somewhere the ID can be dropped.

What the chain actually looks like

Before the failure modes make sense, it helps to see the whole path written down. Most people have only ever seen their own end of it.

  1. Your link. Someone clicks a URL that points at your tracker, not at the advertiser.
  2. Your tracker mints an ID. A unique string for this click, stored on your side with the source, the creative and the timestamp.
  3. A redirect to the offer. Your tracker sends the browser onward to the advertiser's landing page, appending that ID to the URL as a parameter.
  4. The advertiser captures it. Their page reads the parameter and stores it. In a cookie, in session storage, in a hidden form field, or against a user record if the visitor signs in.
  5. A conversion happens. Possibly minutes later, possibly on another device, possibly after a support call.
  6. Their server fires a postback. A server-to-server HTTP request back to your endpoint, carrying the ID, an order value, and ideally a transaction ID.
  7. Your tracker matches. ID found, conversion recorded, attributed to the original click.

Seven steps. At least three parties. No single owner. The ID has to be written correctly at step 3, read correctly at step 4, survive whatever happens between steps 4 and 5, and be echoed back accurately at step 6. Any of those can fail without anything else appearing wrong.

Which is honestly the whole problem in one sentence. A chain of handoffs where nobody sees the full picture, and where a silent failure at any step ends the same way. You don't get paid.

And nobody in that chain is incentivised to look upstream on your behalf. Your tracker is looking at what arrived. The advertiser is looking at what left their box. The gap in the middle is yours to inspect.

The six places the ID actually gets lost

In roughly the order I see them.

1. It was never in the offer URL. The click ID exists in your tracker, but nobody appended it to the destination URL. Nothing to hand back. This is the most common one. Also the most embarrassing, because it's entirely preventable in thirty seconds.

2. The macro didn't resolve. You put {clickid} in the URL and the platform expected {click_id}. Or the reverse. The unresolved token gets passed through as literal text. Your tracker then tries to match a conversion to a click called {clickid}, which does not exist.

3. The parameter names don't agree. You send subid. They store aff_sub. They post back aff_sub. Your tracker is listening for subid. Both sides are working perfectly. Neither can hear the other.

4. A redirect in the chain strips it. Every hop between the click and the landing page is a chance to lose the query string. Long chains, shorteners, geo-redirects and consent gateways are all common culprits. The more intermediate hops, the more likely one of them rebuilds the URL from scratch and drops what it doesn't recognise.

5. The session broke. The ID reached the landing page but the conversion happened somewhere else. A different subdomain. An app handoff. A checkout on a separate host. A purchase completed days later on another device. Whatever was holding the ID didn't make it across.

6. The postback fired but nobody was listening. The advertiser sent it. Your endpoint returned an error, timed out, or sat behind a firewall rule. Many platforms fire once and never retry.

Notice something. Only one of these is really about the postback URL. The rest happen upstream, before anyone gets near the part most guides spend their time on.

Failure two deserves its own paragraph

The macro mismatch is worth singling out because it's the one that looks correct in every screenshot.

There is no standard for what these placeholders are called. Different platforms use different token syntax and different parameter names for the same concept, and both sides usually assume theirs is the obvious one. You will see the same value called click_id, clickid, cid, transaction_id, subid, sub1, aff_sub and s1 depending on whose documentation you're reading. And the token wrapping it may be {...}, {{...}}, [...] or ##...##.

That means "I put the macro in" is not a verified state. The only verified state is I clicked the link and saw a real value in the address bar. An unresolved macro is visually almost identical to a resolved one at a glance. {clickid} and a7f3c9d2 both look like something is there. Read the actual characters. Not the shape of the string. The characters.

The postback itself: four things worth getting right

The postback is the shortest part of the chain. It's also the part most likely to be set up once and never inspected.

Send a transaction ID, not just a click ID. The click ID says which click. A transaction ID (your order number, your payment reference, anything already unique in the advertiser's own system) says which sale. Without it, you have no way to tell a genuine second purchase from the same postback arriving twice. And platforms do resend. Reuse an existing order ID rather than inventing a new identifier space. It's already unique and it's already in the advertiser's database when you need to look something up.

Test what happens on a duplicate. Fire the same postback twice, deliberately, with identical click ID and transaction ID. One payable conversion should be recorded. The second should be logged as a duplicate. If two appear, you have a system that will inflate your own numbers and destroy your credibility in the first dispute you raise.

Log the raw request, not just the parsed result. When a discrepancy appears three weeks later, "our tracker shows no conversion" is a weak position. "Here is the raw inbound request we received at 14:22:07, with these parameters and this response code" is a strong one. Keep the raw log for at least as long as your longest payment cycle.

Check the response code. Your endpoint should return 200. A redirect, a 403 from a firewall rule, or a timeout under load all look like silence from the advertiser's side. And most platforms fire once and move on. If your tracker offers a delivery log for inbound postbacks, read it during the first week. It's the cheapest early warning available.

Why the browser is now the weakest link

Server-to-server isn't a preference. It's a response to browsers deliberately becoming worse at exactly the thing affiliate tracking used to rely on.

Safari's WebKit announced full third-party cookie blocking in March 2020, "cookies for cross-site resources are now blocked by default across the board", alongside "deleting all of a website's script-writable storage after seven days of Safari use without user interaction on the site", which covers LocalStorage, IndexedDB, SessionStorage and Service Worker registrations (WebKit: Full Third-Party Cookie Blocking and More). Other browsers have moved in the same direction at their own pace.

What that means in practice, for anyone running offers:

  • A conversion that happens more than a few days after the click is the fragile case, not the exception. Long consideration cycles (insurance, lending, travel, anything with a comparison step) are exactly where browser-held identifiers expire before the sale lands.
  • Anything depending on a third-party cookie is already gone on a meaningful share of traffic. If your setup still leans on one, your reported conversion rate is not a measure of your offer.
  • The click ID must be captured server-side, or into first-party storage under the advertiser's own domain, at the moment of arrival. Whatever holds it has to belong to the site the user is actually on.

Which is also why pixel-versus-postback isn't really a debate any more. Use a pixel only where server-to-server is genuinely not offered. Treat those campaigns as measured with a known undercount rather than measured accurately.

The ten-minute test that prevents almost all of it

Before a campaign takes real budget, run one conversion through the entire system yourself.

  1. Click your own tracking link. Not the offer URL. Your link, the way a real user would arrive.
  2. Look at the address bar on the landing page. Is your click ID actually there, in full? Copy it somewhere. If it's missing at this step, stop. Nothing downstream can save you.
  3. Complete the action. Buy, register, submit. Whatever counts as a conversion. Use a test mode if the advertiser has one.
  4. Check that the conversion appears in your tracker, and that it carries the exact ID you copied in step 2.
  5. Check the advertiser's side too. Both systems should show one conversion with the same identifier.

If all five line up, your tracking works. If step 2 fails, it's a link problem. If step 4 fails but the advertiser recorded the sale, it's a postback problem. That single distinction tells you which half of the system to look at. Which is most of the diagnosis, actually.

Ten minutes before launch. Compare that to a month of disputed conversions. This is the affiliate-specific version of the more general "verify tracking before you run anything" discipline that sits at day 14-30 of the first 90 days of an agency account, in the order they actually matter. Same rule, sharper edges, higher cost of getting it wrong because the reconciliation is with a third party who won't pay you if the numbers don't line up.

Two additions worth making once you've done this a few times. Run it on a phone as well as a desktop. App handoffs and in-app browsers break sessions in ways desktop testing will never reveal. And run it from the geography the traffic will come from if the offer has geo-redirects. A redirect that only fires for certain countries is a redirect you will never see from your own desk.

Download the pre-launch tracking check. The five steps above, the six failure points, and the discrepancy table, on one sheet. Free, no signup. Works in Excel, Google Sheets and Numbers.

When the numbers disagree anyway

They will, sometimes. Some gap is normal. Different attribution windows, different timezones, cancelled and returned orders, deduplication rules. A small, stable discrepancy is a fact of the channel, not a bug.

Worth separating the two things that can be wrong here, because they look identical from a dashboard. Everything in this post is about the technical chain failing. The other possibility is that the chain is intact and the rule is the problem, which is a negotiation rather than a bug — attribution windows are a commercial term, not a technical setting covers the case where a partner asks to widen the window and what it actually costs to agree.

What matters is the shape of it.

Shape of the gap Most likely cause Where to look first
Small and steady, month after month Definitional (windows, timezones, refund handling) Write both definitions down once, then stop relitigating
Sudden, from a specific date Something shipped Release notes, consent banner changes, new redirect
One traffic source only That source's redirect chain The hops between that source and the landing page
Total (you have nothing) The ID never arrives Back to the ten-minute test
Yours is higher than theirs Duplicates on your side Your dedupe rule and your transaction IDs

That last row is the one people forget. And it's the most dangerous to your position. If you raise a dispute using numbers inflated by double-counted postbacks, you lose the argument and some standing with it. Check your own side first, every time, before writing the email.

All of these diagnoses assume you have a number of your own to compare against. If you don't (if every figure in the conversation came from the network) then there's no shape to read and no discrepancy to investigate. Only a payment to accept. That's the strongest practical argument for owning a click layer before you need one. It's also why starting affiliate marketing without a website is fine right up until the first month the numbers look wrong.

Untrusted numbers are also unusable numbers. CAC, ROAS and payback all read off this data, which is why the metrics guide is worth nothing until the tracking is proven.

The useful instinct is to check whether the pattern changed, not whether the numbers match exactly. Exact matching isn't the goal and never was.

How to raise a discrepancy so it actually gets fixed

When you do have to escalate, the difference between a resolved ticket and a three-week thread is entirely in what you attach.

Bring four things. The date range. Your count against theirs. A handful of specific click IDs you can prove converted. And the raw log line showing what arrived at your endpoint and when. Specific IDs are what turn the conversation from a disagreement about totals into a lookup somebody can perform. A partner manager can search a transaction ID in thirty seconds. They cannot do anything at all with "we think we're being underpaid by around eight per cent".

Ask one narrow question rather than making an accusation. Can you confirm whether a conversion was recorded against click ID X on this date, and whether a postback was attempted? You want the answer, not the argument. And whatever comes back (including "no postback was attempted, that is on us") write down the definition you both settled on. Half of all recurring discrepancies are the same misunderstanding being rediscovered every month by different people.

The habit that actually matters

Test tracking as a launch step, not a troubleshooting step. That's really the whole rule.

Every campaign. Every new offer. Every time the advertiser redesigns their checkout. Every time someone adds a redirect "temporarily". Not because tracking is fragile in some mysterious way. Because it depends on a value surviving a chain of systems that nobody owns end to end. And any one of them can change without telling you.

There's a second habit worth adding. Re-run the ten-minute test on a schedule for anything that carries real budget, not only at launch. Nothing tells you when the advertiser's checkout changed. The first signal is a month of missing conversions, and by then the evidence has aged past the point where anyone can reconstruct it.

This is the unglamorous half of the performance marketing playbook. You cannot optimise toward a profit metric you cannot measure. The people who lose money on affiliate tracking are rarely the ones who don't understand postbacks. They're the ones who found out in week four what they could have found out in minute ten.

FAQs

Why do my numbers never exactly match the network's?

Because you're counting slightly different things. Attribution windows, timezones, refund and cancellation handling, and deduplication rules all differ between platforms. A small consistent gap is normal. Agree on the definitions once, document them, and only investigate when the pattern changes. Chasing an exact match is a fight nobody wins.

Should I use a pixel or a server-to-server postback?

Server-to-server, wherever the advertiser supports it. Pixels run in the browser, so ad blockers, privacy settings, tracking prevention and users closing the tab early all cost you conversions. A postback is a server talking to a server and isn't affected by any of that. Use a pixel only when S2S isn't offered, and treat those campaigns as carrying a known undercount.

What is the single most common tracking mistake?

The click ID never making it into the offer URL in the first place. It's trivially avoidable and it silently invalidates the entire chain. Every downstream setting can be perfect and you'll still get paid for nothing.

What is the difference between a click ID and a transaction ID?

The click ID identifies which click. It's minted by your tracker when someone clicks your link, and it's what attribution matches on. The transaction ID identifies which sale, usually the advertiser's own order or payment reference. You need both. The click ID to attribute, and the transaction ID to deduplicate. Without a transaction ID you can't tell a genuine repeat purchase from the same postback arriving twice.

How long should a click ID stay valid?

As long as your agreed attribution window, and no longer. The important part is that both sides use the same number and the same timezone. A seven-day window measured in IST and a seven-day window measured in UTC will disagree on every conversion that lands near midnight. Which is a steady, unexplained gap that survives every investigation until somebody finally compares clocks.

Do browser privacy changes break affiliate tracking?

They break the browser-dependent parts of it. Safari's WebKit has blocked cross-site cookies by default since March 2020 and clears script-writable storage after seven days without interaction on the site, and other browsers have moved similarly. Server-to-server postbacks are unaffected, which is why long-consideration offers in particular should be measured that way rather than by anything held in the browser.

How do I test tracking without spending money?

Ask the advertiser for a test mode, a coupon that zeroes the order, or a staging endpoint. If none exists, make one real minimum-value purchase yourself and refund it afterwards. And tell the partner manager you're doing it, so the refund doesn't look like fraud. The cost of one test order is always smaller than one disputed month.

Top comments (0)