API: tracking and postback
MeerPartners tracking endpoints: redirect /r/{code}, pixel /p/{code}.gif and conversion intake POST /api/v1/postback with an HMAC signature.
Tracking is the "hot path" of attribution: a link click records a click and sets a cookie, and the conversion comes back via an S2S postback. The redirect and the pixel live outside the version (/r/..., /p/...) for speed; the postback is under /api/v1. Step-by-step guides: Tracking links and Postback.
GET /r/{code}
/r/{code}🔒 —Redirect via a tracking link. Resolves the offer, records the click (asynchronously, without blocking the response), sets a signed attribution cookie aff_attr and returns a 302 to the landing. This is "always-302": even if the write fails, the click goes to a fallback log and the redirect still happens.
Path: code — the short link code (from POST /affiliate/links).
Query parameters (optional):
sub_id1stringoptionalsub1.sub_id2stringoptionalsub2.sub_id3stringoptionalsub3.sub_id4stringoptionalsub4.sub_id5stringoptionalsub5.Response: 302 Found. The Location header points to the offer landing with an appended aff_click=<click_uid> (a server-side attribution fallback for Telegram/WebView, where the cookie is often lost). The aff_attr cookie is set (HMAC-signed, Max-Age = the offer's attribution window, SameSite=Lax). For bots the cookie is not set.
HTTP/1.1 302 Found
Location: https://acme.com/landing?utm=promo&aff_click=8f1c0a2e-...
Set-Cookie: aff_attr=eyJ...; Max-Age=2592000; Path=/; SameSite=LaxErrors: TRACKING_LINK_NOT_FOUND (404 — unknown code), LINK_INACTIVE (410 — the link is disabled, no fallback), OFFER_INACTIVE (410 — the offer is inactive, no fallback), OFFER_NO_LANDING (503 — an active offer has no landing set), TRACKING_RATE_LIMITED (429). If a fallback_url is present, an inactive link/offer redirects to it instead of 410.
GET /p/{code}.gif
/p/{code}.gif🔒 —Pixel fallback: the same click and cookie recording, but instead of a redirect it returns a transparent 1×1 GIF. Convenient to embed in an <img> where you cannot redirect (emails, widgets).
Path/Query — same as /r/{code} (sub_id1..5).
Response: 200 OK, Content-Type: image/gif, the header Cache-Control: no-store, no-cache, must-revalidate, private, body — a 43-byte GIF. The aff_attr cookie is set the same way (except for bots).
POST /api/v1/postback
/api/v1/postback🔒 HMACConversion intake S2S from the business. Signed with the tenant_api_key with the postback scope. The HMAC is computed over the raw request body — do not reassemble the JSON after signing.
Headers
X-Api-Key-Idstringrequiredkey_id).X-TimestampstringrequiredPOSTBACK_STALE_TIMESTAMP.X-Signaturestringrequiredhex(HMAC_SHA256(secret, signing_string)).X-Tenant-Idstringoptionalsigning_string = "{method}\n{path}\n{X-Timestamp}\n{sha256_hex(raw_body)}". A full breakdown and signature examples in different languages are on the Postback page.
Body
external_order_idstringrequiredevent_typestringrequiredsale, lead, registration, refund, chargeback.refstringoptionalclick_id (a.k.a. click_uid) or the link code. Up to 128 characters.sub_idstringoptionalamountstringoptionalsale (together with currency).currencystringoptionalsale.statusstringoptionalapproved or pending.occurred_atstringoptionalmetaobjectoptional{
"external_order_id": "ORD-100500",
"event_type": "sale",
"ref": "8f1c0a2e-7b3d-4c1a-9f2e-1a2b3c4d5e6f",
"amount": "2990.00",
"currency": "RUB",
"status": "approved",
"occurred_at": "2026-06-14T12:30:00Z"
}Response
acceptedbooleanrequiredduplicatebooleanrequiredtrue if it is a repeat by external_order_id (an idempotent replay).conversion_idintegeroptionalcommissionobjectoptionalreasonstringoptionalaccepted:false (for example POSTBACK_UNKNOWN_REF, POSTBACK_REFUND_NO_CONVERSION).{
"accepted": true,
"duplicate": false,
"conversion_id": 778,
"commission": {
"id": 9100,
"amount": "750.00",
"currency": "RUB",
"status": "pending",
"hold_until": "2026-06-28T12:30:00Z"
}
}{ "accepted": true, "duplicate": true, "conversion_id": 778 }2xx even on accepted:false
Logically processed events (including "no click found by ref" and a repeat) return 200 with the accepted/duplicate/reason flags. HTTP errors (4xx/5xx) are only for authentication, signature, rate-limit or validation problems. More on idempotency and dedup is on the Rate limits and idempotency page.
Errors: TENANT_API_KEY_INVALID (401), TENANT_API_KEY_SCOPE_DENIED (403 — no postback scope), POSTBACK_BAD_SIGNATURE (401), POSTBACK_STALE_TIMESTAMP (400), POSTBACK_REPLAY (409 — repeated signature), POSTBACK_TENANT_MISMATCH (403), POSTBACK_MISSING_FIELD (422 — a required field is missing), POSTBACK_RATE_LIMITED (429), TENANT_SUSPENDED (403).