Troubleshooting: conversion not recorded
Work through this top to bottom - it's ordered by how often each cause actually happens. Every postback you send, valid or not, is logged on affilink's side, so nothing is ever silently lost: if you got an HTTP response, the attempt was recorded.
First: what response did you get?
Log the HTTP status and body of every postback call, at least while
integrating. Each status points to exactly one section below. If you
got a 200 - the conversion was recorded; the
problem is elsewhere (wrong dashboard filter, test mode, refund).
401 - signature rejected (the most common one)
Three causes, in order of likelihood:
- The body you signed isn't the body you sent. The
signature covers the exact raw bytes of the JSON body. If your
HTTP client re-serializes the object after you signed it (reordering
keys, changing
149.9to149.90, adding whitespace), the signature no longer matches. Fix: build the JSON string once, sign that string, and send that string - never sign the object and send a re-encode of it. - Wrong secret. The secret is per offer - not
per organization, not per API key. Reporting for offer B with offer A's
secret fails. Check you're using the
webhook_secretshown on the same offer whoseoffer_idis in the payload. - Stale timestamp.
X-Affilink-Timestampmust be within 300 seconds of real time, in unix seconds (10 digits) - not milliseconds (13 digits). A server with clock drift or a cached/retried request with the original timestamp both fail.
Quick self-check - recompute what the signature should be:
node -e "
const crypto = require('crypto');
const secret = 'YOUR_WEBHOOK_SECRET';
const ts = 'THE_TIMESTAMP_HEADER_YOU_SENT';
const body = 'THE_EXACT_RAW_BODY_STRING_YOU_SENT';
console.log(crypto.createHmac('sha256', secret).update(ts + '.' + body).digest('hex'));
" If this doesn't equal the header you sent, the bug is in your signing code.
404 - click_id not found
- The cookie was never set. Visit your own tracking link and check dev tools → Application → Cookies for
_affilink_click. If it's missing, your step-1 snippet isn't running on the landing page (wrong page template? script blocked?). - The value got mangled. The click_id is a UUID (36 chars, four dashes). Double URL-encoding, truncation by a cookie-size trimmer, or reading the wrong cookie all produce a value affilink never issued.
- You're testing with an invented click_id. Only IDs minted by a real click on
go.affilink.co.il/r/...exist. Always test by clicking your own tracking link first.
409 - duplicate order id
This is not an error. It means this
external_order_id was already recorded; the response body
returns the original conversion_id. Treat it as success.
You'll see it whenever a retry fires or an order-completion hook runs
twice - which is exactly the double-counting protection working.
410 - attribution window expired
The click exists, but it's older than the offer's attribution window.
The usual cause: your cookie lives longer than the window
(e.g. cookie set to 90 days, offer window is 30), so late buyers carry a
click_id that's no longer valid. Either align the cookie's
max-age to the window, or ask the organization to extend the
offer's window - that's a business decision, not a bug.
422 - malformed payload
- A required field is missing or the wrong type (
amountmust be a JSON number, not a string). is_renewal: truefor asubscription_idthat never had a first (non-renewal) charge recorded.click_idomitted on a non-renewal event.
429 - rate limited
Back off exponentially and retry - retries are always safe thanks to
external_order_id idempotency. If you hit this in normal
operation (not a backfill loop), contact support.
No conversions at all, but clicks are counting
The classic silent gap - the postback call never fires. Check:
- Async order completion. If orders complete via a payment provider's webhook (not the customer's own browser request), the customer's cookies aren't in that request. You must save the click_id onto the order at checkout time and read it from the order record in the completion hook. This is the single most common integration bug.
- The hook never runs. Add a log line at the top of your completion handler and place a test order.
- Egress blocked. Some hosts block outbound HTTP. Verify your server can reach
https://api.affilink.co.ilat all.
Still stuck?
Collect: the exact raw body, both headers, the HTTP status + response body, and the approximate send time - then contact the organization running the offer (or affilink support for marketplace offers). With those four items the issue is diagnosable in minutes against the server-side log of your request.