Conversion API
The endpoint that receives sales – for server-side and custom integrations, with parameters, responses and limits.
Every sale from track.js ends up at the same endpoint. If you build the integration yourself – e.g. from your own backend once payment has gone through – you can call it directly.
POST https://app.affilyflow.com/api/track/conversion
GET https://app.affilyflow.com/api/track/conversion- Both
GETandPOSTwork.OPTIONSanswers CORS preflight requests. - CORS is open (
Access-Control-Allow-Origin: *), so the endpoint can be called from any browser. - No key is required. What ties the sale to an affiliate is the click ID.
The click ID first
Without the click ID the sale can't be attributed to anyone. It arrives from the affiliate link as ?afid= on your site, and track.js stores it in the af_click cookie on your domain. Your server can therefore read the cookie from the customer's request when the order is created and send it along.
If you don't have track.js on the site, store afid from the URL yourself. Only accept values that look like ours: c_ followed by 4–64 characters (letters, digits, _ and -).
Parameters
| Parameter | Alias | Required | Meaning |
|---|---|---|---|
afid | click_id | Yes | The click ID (c_…). |
store | – | Recommended | Your store ID (the same as data-store). Must match the click's store, otherwise store_mismatch. |
order | order_id | Recommended | The order number. The same number is never counted twice in the same program. At most 120 characters. |
total | order_total | Yes | The amount commission is calculated on: goods after discount, excluding VAT and shipping. A machine number (1249.5). |
currency | – | No | ISO 4217 currency code, e.g. DKK or EUR. Default DKK. |
Legacy direct links without a click ID can send aff (or aff_id) – the affiliate's tracking ID – together with store instead. The affiliate must then have joined the store's program, otherwise the response is not_joined.
GET
Parameters in the URL. This is what track.js uses as a fallback when the browser has no sendBeacon.
curl "https://app.affilyflow.com/api/track/conversion?afid=c_AbC123xYz_-9&store=DIN_BUTIK&order=1008&total=499¤cy=DKK"POST
Send the fields as JSON in the body. The fields can also be in the URL – if a field is in both, the body wins. This is how track.js sends with navigator.sendBeacon: a POST with the fields in the URL and no body.
curl -X POST https://app.affilyflow.com/api/track/conversion \
-H "Content-Type: application/json" \
-d '{"afid":"c_AbC123xYz_-9","store":"DIN_BUTIK","order":"1008","total":499,"currency":"DKK"}'await fetch('https://app.affilyflow.com/api/track/conversion', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
afid: order.afClick, // saved from the af_click cookie
store: 'DIN_BUTIK',
order: order.number,
total: order.subtotalExVat, // goods after discount, excl. VAT and shipping
currency: order.currency
})
});The body may be at most 64 KB.
Optional body fields
| Field | Meaning |
|---|---|
items | Item lines: a list of { title, variant, qty, price, product_id }. title is required per line. At most 50 lines; long text is truncated. |
subtotal, tax, shipping | The order breakdown (numbers, 0 or above). See below. |
buyer_hash | Fingerprint of the buyer's email: hex(sha256(lowercase(trim(email)))) – 64 hex characters. If it matches the affiliate's email, the sale is recorded as rejected (self_referral). The email itself is never sent. |
The breakdown: if you send total as the order's full amount and include subtotal, commission is calculated on subtotal instead – but never on more than total:
- If
taxis 0 or missing, orsubtotal + shipping + tax = total(VAT added on top), the base issubtotal. - If
subtotal + shipping = total(VAT included in prices), the base issubtotal × (total − tax) / total. - Otherwise the cautious
min(subtotal, total − tax − shipping)is used.
The simplest is to send total as the goods amount excluding VAT and shipping and leave out the breakdown.
Responses
The response is always JSON with a status. What each status means and what to do is in Status codes.
| HTTP | Body | When |
|---|---|---|
| 200 | { "status": "ok" } | The sale is recorded. |
| 200 | { "status": "duplicate" }, "expired" etc. | The request was processed, but the sale earned no commission. |
| 404 | { "status": "unknown" } | The click ID doesn't exist. |
| 400 | { "status": "error", "reason": "…" } | Neither afid nor aff + store was included. |
| 413 | { "status": "too_large" } | The body was over 64 KB. |
| 429 | { "status": "rate_limited" } | Too many calls from the same IP address. |
| 500 | { "status": "error" } | Recording failed. Try again. |
A recorded sale awaits the store's approval. Commission is only credited once the sale is approved.
Duplicates and order numbers
- The same order number in the same program is never counted twice – the second call responds
duplicate. It is therefore safe to resend when in doubt. - Without an order number there is no duplicate check on the server. Always send the order number.
- An unfilled placeholder (
DIN_ORDRE_ID,ORDER_ID,{{ … }},[ … ],< … >,% … %etc.) is treated as no order number, so it never takes up the number for every later sale.
Currency
The sale can be in any currency with a rate from the European Central Bank. It is converted to the store's settlement currency before commission and fee are calculated, and the rate is locked on the sale. A currency code that isn't three letters, or a currency without a rate from the last 10 days, is rejected as unsupported_currency.
Limits
- The endpoint is rate limited per IP address. The limit doesn't affect a store's own server in normal use; the response is
429withrate_limitedif it is reached. - Each program has an amount cap per order (50,000 in the settlement currency by default). An order above the cap is rejected as
value_too_high– typically an amount sent in cents. - Long values are truncated rather than rejected: click ID 64 characters, store and order number 120 characters.
Refunds
The endpoint only reports sales. If an order is refunded, the store rejects the sale under Sales in the dashboard within the program's approval period. /api/track/conversion/reverse is used by the WooCommerce plugin and requires the store's key – it is not a public route.
Missing something?
Write to us – we answer quickly, and the page gets fixed.