affilink

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:

  1. 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.9 to 149.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.
  2. 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_secret shown on the same offer whose offer_id is in the payload.
  3. Stale timestamp. X-Affilink-Timestamp must 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

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

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:

  1. 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.
  2. The hook never runs. Add a log line at the top of your completion handler and place a test order.
  3. Egress blocked. Some hosts block outbound HTTP. Verify your server can reach https://api.affilink.co.il at 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.