MMeerPartners docs

Постбэк (S2S конверсии)

Серверный postback MeerPartners: как сообщить платформе о конверсии. Поля запроса, идемпотентность по external_order_id, ответы и коды отклонения. Примеры на cURL, Node.js и Python.

Postback — это серверное уведомление от вашего бэкенда платформе о том, что у вас произошла конверсия: продажа, лид, регистрация, возврат или чарджбэк. Это главный механизм интеграции для бизнеса: именно по postback платформа создаёт конверсию, атрибутирует её партнёру и начисляет комиссию.

POST/api/v1/postback🔒 HMAC

Запрос подписывается HMAC по секрету API-ключа со скоупом postback. Алгоритм подписи и заголовки (X-Api-Key-Id, X-Timestamp, X-Signature, опционально X-Tenant-Id) — на странице API-ключи и подпись. Без валидной подписи запрос не обрабатывается.

Сначала — клик

Чтобы конверсию было кому атрибутировать, у вас должен быть click_id пользователя. Его выдаёт трекинг-ссылка партнёра как aff_click при редиректе. Сохраните его рядом с заказом и передайте в postback как ref. См. Трекинг-ссылки и макросы.

Поля запроса

Тело — JSON. Content-Type: application/json.

external_order_idstringrequired

Уникальный ID заказа/события в вашей системе (1–128 символов). Это ключ идемпотентности и дедупликации — по нему платформа отличает повтор от нового события.

event_typestringrequired

Тип события: sale (продажа), lead (лид), registration (регистрация), refund (возврат), chargeback (чарджбэк).

refstringoptional

Атрибуция: click_id (значение aff_click из редиректа) или code трекинг-ссылки. Обязателен для sale/lead/registration; для refund/chargeback не нужен (событие находит исходную конверсию по external_order_id).

sub_idstringoptional

Сквозная метка партнёра (до 128 символов), если вы её прокидывали и хотите сохранить в конверсии.

amountstringoptional

Сумма заказа строкой (чтобы не терять точность), например "19.99". Обязательна для sale вместе с currency.

currencystringoptional

Валюта суммы, ISO-4217, 3 буквы — "USD", "RUB". Обязательна для sale.

statusstringoptional

approved или pending. Влияет на старт конверсии; по умолчанию конверсия стартует в pending и проходит hold-период.

occurred_atstringoptional

Время события на вашей стороне в формате RFC 3339 (2026-06-01T10:15:00Z). По умолчанию — момент приёма.

metaobjectoptional

Произвольные данные (план, промокод и т.п.). Сохраняются в конверсии для аналитики и разбора.

Обязательность зависит от event_type

Для sale нужны external_order_id, event_type, ref, amount, currency. Для lead/registrationexternal_order_id, event_type, ref (сумма необязательна). Для refund/chargeback — только external_order_id и event_type. Если обязательного поля нет — POSTBACK_MISSING_FIELD (422).

Идемпотентность

Платформа дедуплицирует события по паре (ваш tenant, external_order_id) и типу события — под advisory-lock, поэтому даже гонка двух одинаковых postback не создаст две конверсии.

  • Первый успешный postback создаёт конверсию и комиссию.
  • Повтор с тем же external_order_id → тот же результат и "duplicate": true, без побочных эффектов.

Это значит, что ретраить безопасно: повторная отправка не задвоит начисление.

Ответы

Ответ — «голый» JSON-объект (без обёртки data). HTTP-код 200 при успешной или дублирующей обработке.

Успех — sale атрибутирован
{
  "accepted": true,
  "duplicate": false,
  "conversion_id": 7741,
  "commission": {
    "id": 7741,
    "amount": "2.50",
    "currency": "USD",
    "status": "pending",
    "hold_until": "2026-06-15T10:15:00Z"
  }
}
Идемпотентный повтор
{ "accepted": true, "duplicate": true, "conversion_id": 7741 }
Принято, но без атрибуции (органика)
{ "accepted": false, "reason": "POSTBACK_UNKNOWN_REF" }
Поле ответаЗначение
acceptedtrue, если событие принято в обработку; false для органики/неприменимого refund
duplicatetrue, если это повтор уже обработанного external_order_id
conversion_idID созданной (или найденной) конверсии
commissionКраткая инфо по комиссии нулевого уровня: id, amount, currency, status, hold_until (только при успешной атрибуции на партнёра)
reasonПричина, когда accepted: false (например POSTBACK_UNKNOWN_REF)

accepted:false — это не ошибка ретрая

Ответ 200 с accepted: false (нет атрибуции или refund на несуществующую конверсию) — финальный. Это не сбой связи: значит, продажа органическая или клик не нашёлся. Не ретрайте такие ответы по кругу — обрабатывайте как штатный исход. Ретрай оправдан только на 5xx/таймаут.

Коды отклонения

КодHTTPКогдаРеакция
TENANT_API_KEY_INVALID401Ключ неизвестен, отозван или истёкПроверьте X-Api-Key-Id/секрет, не ретрайте
TENANT_API_KEY_SCOPE_DENIED403У ключа нет скоупа postbackВыпустите ключ со скоупом postback
POSTBACK_BAD_SIGNATURE401HMAC-подпись не сошласьПроверьте алгоритм подписи и что подписали те же байты
POSTBACK_STALE_TIMESTAMP400X-Timestamp вне окна ±300 сСинхронизируйте часы (NTP), пересоберите запрос
POSTBACK_REPLAY409Повтор той же подписи в окне памятиСформируйте новый запрос с актуальным X-Timestamp
POSTBACK_TENANT_MISMATCH403X-Tenant-Id не совпал с tenant ключаУберите/исправьте X-Tenant-Id
POSTBACK_MISSING_FIELD422Нет обязательного поля для event_typeДобавьте недостающее поле
POSTBACK_UNKNOWN_REF200Нет атрибутируемого клика (accepted:false)Штатный исход — не ретрайте
POSTBACK_REFUND_NO_CONVERSION200Refund на несуществующую конверсию (accepted:false)Штатный исход — не ретрайте
POSTBACK_RATE_LIMITED429Слишком много postback на ключСбавьте темп, ретрай с backoff
SERVICE_UNAVAILABLE503БД/хранилище недоступныРетрай с экспоненциальным backoff

Полный формат конверта ошибки ({ "error": { "code", "message", "details" } }) — на странице Коды ошибок API.

Стратегия ретраев

Ретрайте только 5xx и сетевые таймауты — с экспоненциальным backoff. На любой 2xx (включая accepted:false) — стоп. На 4xx (кроме 409 POSTBACK_REPLAY, где нужно пересобрать подпись) — стоп и алерт: это, как правило, баг интеграции, а не временный сбой.

Полный пример

Конверсия-продажа с вычислением подписи. Базовый URL — {API_BASE} (точный домен даёт оператор).

API_BASE="https://api.meerpartners.com"
KEY_ID="key_3f9a1c"
SECRET="sk_live_9f2a…"
 
BODY='{"external_order_id":"ORD-558123","event_type":"sale","ref":"clk_5f2a9e","amount":"19.99","currency":"USD","status":"approved"}'
TS=$(date +%s)
BODY_HASH=$(printf '%s' "$BODY" | openssl dgst -sha256 -hex | awk '{print $NF}')
SIGNING=$(printf '%s\n%s\n%s\n%s' "POST" "/api/v1/postback" "$TS" "$BODY_HASH")
SIG=$(printf '%s' "$SIGNING" | openssl dgst -sha256 -hmac "$SECRET" -hex | awk '{print $NF}')
 
curl -sS -X POST "$API_BASE/api/v1/postback" \
  -H "Content-Type: application/json" \
  -H "X-Api-Key-Id: $KEY_ID" \
  -H "X-Timestamp: $TS" \
  -H "X-Signature: $SIG" \
  --data "$BODY"

Проверьте без боя

Перед подключением живого потока прогоните тестовый постбэк из кабинета (POST /api/v1/merchant/integration/test-postback) — он проходит через реальный флоу, но безопасен для проверки связки ключ ↔ оффер.

Что дальше