MMeerPartners docs

API: трекинг и постбэк

Трекинговые эндпоинты MeerPartners: редирект /r/{code}, пиксель /p/{code}.gif и приём конверсий POST /api/v1/postback с HMAC-подписью.

Трекинг — это «горячий путь» атрибуции: переход по ссылке фиксирует клик и ставит cookie, а конверсия приходит обратно через S2S-постбэк. Редирект и пиксель живут вне версии (/r/..., /p/...) ради скорости; постбэк — под /api/v1. Пошаговые гайды: Трекинг-ссылки и Постбэк.

GET /r/{code}

GET/r/{code}🔒

Редирект по трекинг-ссылке. Резолвит оффер, фиксирует клик (асинхронно, не блокируя ответ), ставит подписанную cookie атрибуции aff_attr и отдаёт 302 на лендинг. Это «always-302»: даже при сбое записи клик уходит в fallback-лог, а редирект всё равно происходит.

Path: code — короткий код ссылки (из POST /affiliate/links).

Query-параметры (опционально):

sub_id1stringoptional
Метка для аналитики (усекается до 128 символов). Принимается также как sub1.
sub_id2stringoptional
Метка. Также sub2.
sub_id3stringoptional
Метка. Также sub3.
sub_id4stringoptional
Метка. Также sub4.
sub_id5stringoptional
Метка. Также sub5.

Ответ: 302 Found. Заголовок Location ведёт на лендинг оффера с добавленным aff_click=<click_uid> (server-side fallback атрибуции для Telegram/WebView, где cookie часто теряется). Ставится cookie aff_attr (HMAC-подпись, Max-Age = окно атрибуции оффера, SameSite=Lax). Для ботов cookie не ставится.

Пример ответа
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=Lax

Ошибки: TRACKING_LINK_NOT_FOUND (404 — неизвестный код), LINK_INACTIVE (410 — ссылка отключена, нет fallback), OFFER_INACTIVE (410 — оффер неактивен, нет fallback), OFFER_NO_LANDING (503 — у активного оффера не задан landing), TRACKING_RATE_LIMITED (429). При наличии fallback_url неактивная ссылка/оффер редиректят на него вместо 410.

GET /p/{code}.gif

GET/p/{code}.gif🔒

Пиксель-фоллбэк: то же фиксирование клика и cookie, но вместо редиректа отдаётся прозрачный 1×1 GIF. Удобно вставлять в <img> там, где нельзя редиректить (письма, виджеты).

Path/Query — как у /r/{code} (sub_id1..5).

Ответ: 200 OK, Content-Type: image/gif, заголовок Cache-Control: no-store, no-cache, must-revalidate, private, тело — 43 байта GIF. Cookie aff_attr ставится так же (кроме ботов).

POST /api/v1/postback

POST/api/v1/postback🔒 HMAC

Приём конверсии S2S от бизнеса. Подписывается ключом tenant_api_key со скоупом postback. HMAC считается по сырому телу запроса — не пересобирайте JSON после подписи.

Заголовки

X-Api-Key-Idstringrequired
Публичный идентификатор ключа (key_id).
X-Timestampstringrequired
Unix-время (epoch, сек). Должно быть в пределах ±300 c от серверного, иначе POSTBACK_STALE_TIMESTAMP.
X-Signaturestringrequired
hex(HMAC_SHA256(secret, signing_string)).
X-Tenant-Idstringoptional
ID бизнеса. Источник доверия — ключ в БД; tenant из тела игнорируется.

signing_string = "{method}\n{path}\n{X-Timestamp}\n{sha256_hex(raw_body)}". Полный разбор и примеры подписи на разных языках — на странице Постбэк.

Тело

external_order_idstringrequired
Идентификатор заказа на стороне бизнеса (1–128 символов). Ключ дедупликации/идемпотентности.
event_typestringrequired
Тип события: sale, lead, registration, refund, chargeback.
refstringoptional
click_id (он же click_uid) или код ссылки. До 128 символов.
sub_idstringoptional
Доп. метка партнёра (до 128 символов).
amountstringoptional
Сумма заказа (NUMERIC). Обязательна для sale (вместе с currency).
currencystringoptional
Валюта (ISO-код). Обязательна для sale.
statusstringoptional
approved или pending.
occurred_atstringoptional
Время события (ISO 8601). По умолчанию — момент приёма.
metaobjectoptional
Произвольные доп. данные.
Запрос
{
  "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"
}

Ответ

acceptedbooleanrequired
Принято ли событие к обработке.
duplicatebooleanrequired
true, если повтор по external_order_id (идемпотентный реплей).
conversion_idintegeroptional
ID созданной/найденной конверсии.
commissionobjectoptional
Начисленная комиссия (если есть).
reasonstringoptional
Причина при accepted:false (например POSTBACK_UNKNOWN_REF, POSTBACK_REFUND_NO_CONVERSION).
Ответ 200 (конверсия засчитана)
{
  "accepted": true,
  "duplicate": false,
  "conversion_id": 778,
  "commission": {
    "id": 9100,
    "amount": "750.00",
    "currency": "RUB",
    "status": "pending",
    "hold_until": "2026-06-28T12:30:00Z"
  }
}
Ответ 200 (повтор)
{ "accepted": true, "duplicate": true, "conversion_id": 778 }

2xx и при accepted:false

Логически обработанные события (включая «не нашли клик по ref» и повтор) возвращают 200 с флагами accepted/duplicate/reason. HTTP-ошибки (4xx/5xx) — только для проблем аутентификации, подписи, лимита или валидации. Подробнее об идемпотентности и дедупе — на странице Лимиты и идемпотентность.

Ошибки: TENANT_API_KEY_INVALID (401), TENANT_API_KEY_SCOPE_DENIED (403 — нет скоупа postback), POSTBACK_BAD_SIGNATURE (401), POSTBACK_STALE_TIMESTAMP (400), POSTBACK_REPLAY (409 — повтор подписи), POSTBACK_TENANT_MISMATCH (403), POSTBACK_MISSING_FIELD (422 — нет обязательного поля), POSTBACK_RATE_LIMITED (429), TENANT_SUSPENDED (403).

Что дальше