Postback API reference
The server-to-server endpoint a merchant calls to report conversions.
Base URL: https://api.affilink.co.il. For the step-by-step
site setup, start with the integration guide.
Authentication & signing
Every request is signed HMAC-SHA256 with the offer's webhook_secret. A leaked click_id alone can
never fabricate a conversion.
signature_base = "{timestamp}.{raw_json_body}"
signature = hex( HMAC_SHA256(webhook_secret, signature_base) ) Required headers:
X-Affilink-Timestamp: 1735689600 (unix seconds)
X-Affilink-Signature: <hex>
Content-Type: application/json
Requests are rejected with 401 if the timestamp is more than
300 seconds from server time (replay protection) or the signature doesn't
match.
POST /v1/postback
{
"offer_id": "off_8a2f...",
"click_id": "8f14e45f-ceea-4ab9-8c1e-1b3a2f9d0e11",
"external_order_id": "ORDER-10293",
"event_type": "conversion",
"amount": 149.90,
"currency": "ILS",
"occurred_at": "2026-07-12T10:15:00Z"
} | Field | Required | Notes |
|---|---|---|
offer_id | yes | Must belong to the org owning the signing secret. |
click_id | yes, unless is_renewal | Must exist and be inside the offer's attribution window. |
external_order_id | yes | Your own order/charge id - the idempotency key, unique per org per charge. |
event_type | yes | conversion | refund (refund reverses a prior conversion by external_order_id). |
amount | yes | Decimal, major units. The sale amount - never the commission; commissions are computed server-side. |
currency | yes | ISO 4217. |
occurred_at | no | Defaults to receipt time. |
subscription_id | recurring offers only | Your subscription/customer id - same value on the first charge and every renewal. |
is_renewal | no (default false) | true for a renewal charge: omit click_id; identity is inherited from the first charge sharing subscription_id. |
stage | staged offers only | lead | qualified | closed_won | closed_lost - see below. |
Success response (200)
{
"conversion_id": "cv_1a2b...",
"status": "approved",
"affiliate_commission": 14.99,
"platform_fee": 1.50
} Error codes
| Code | Meaning |
|---|---|
401 | Bad/missing signature or stale timestamp. |
404 | click_id not found (or, for a refund, no conversion with that external_order_id). |
409 | external_order_id already processed - idempotent no-op returning the original conversion_id. Instant offers only; staged offers update instead. |
410 | Click found, but outside the offer's attribution window. |
422 | Malformed payload (missing field, renewal without a first charge, etc.). |
429 | Rate limited. Honor Retry-After-style backoff and retry. |
Every request - valid or not - is written to an audit log before the response returns, so "we sent it, you never recorded it" disputes are always debuggable.
Refunds
"event_type": "refund" with the original
external_order_id marks the conversion reversed and claws
back the already-credited commission (immediate-clawback policy - the
recovered amount nets against the affiliate's next payout).
Recurring subscriptions
For offers with recurring commissions: the first charge is a normal
postback plus subscription_id. Renewals send
is_renewal: true with the same subscription_id
and no click_id - attribution is inherited
from the first charge, with no attribution-window re-check. If the offer
caps commissionable months, charges past the cap are tracked but earn
nothing.
Staged conversions (CRM / lead-gen offers)
A B2B deal doesn't close at a checkout. Offers configured as staged send
multiple postbacks with the same external_order_id,
advancing a stage field: lead →
qualified → closed_won (or
closed_lost). Commission is computed only at
closed_won, using the amount sent at that point;
earlier stages are tracking-only (send "amount": 0). Repeat
external_order_ids are updates here, not 409s.
Any CRM that can fire a webhook or a Zapier/Make automation on a
deal-stage change can drive this - the API doesn't care what's on the
other end.
GET fallback (legacy platforms)
For merchant stacks that can only fire a GET. Same validation and idempotency rules; weaker transport (no timestamp/replay window) - use only when POST isn't feasible:
GET https://api.affilink.co.il/v1/postback-get
?offer_id=off_8a2f
&click_id=8f14e45f-...
&external_order_id=ORDER-10293
&amount=149.90
¤cy=ILS
&sig={hex HMAC-SHA256 of "offer_id|click_id|external_order_id|amount|currency"} Commission calculation (for reference)
The merchant only ever sends the sale amount. affilink resolves the rate (a negotiated per-affiliate override wins over the offer default), and computes the commission server-side. Platform fees and marketplace listing fees are charged to the organization on top - they never reduce the affiliate's commission.