Skip to content

Мэтчинг

Мэтчинг закрывает вашу выплату получателю входящими депозитами игроков — своими или других мерчантов платформы. Выплата («сторона B») создаётся через POST /payment/pay-out с isMatching: true, депозиты («сторона A») — через POST /payment/pay-in с invoice-методом sbp_invoice или c2c_invoice. Отдельных эндпоинтов «создать мэтчинг» нет.

Мерчант может быть на обеих сторонах сразу: выплаты и депозиты независимы, и их включает платформа по вашему запросу.

Все запросы merchant API подписываются заголовком X-Secure-Key (API-ключ кабинета); callback-и приходят с заголовком x-signature (HMAC-SHA256 тела, см. Подпись запросов).


POST /payment/pay-out
X-Secure-Key: <api-key>
Content-Type: application/json
{
"amount": 15500,
"currency": "RUB",
"merchantTransactionId": "payout-100500",
"isMatching": true,
"timeoutInMin": 120,
"callbackUrl": "https://merchant.example.com/callback",
"requisite": {
"sbpNumber": "79991234567",
"bank": "SBER",
"ownerName": "Иван Иванов"
}
}
  • merchantTransactionId — ключ выплаты и ключ идемпотентности, не длиннее 255 символов. Поля matchingTransactionId больше нет.
  • callbackUrl — адрес http:// или https://; другой адрес → 400.
  • amount — в пределах лимитов выплаты мерчанта (по умолчанию от 1 000 до 1 000 000 ₽). Вне лимитов → 403 MATCHING_AMOUNT_OUT_OF_LIMITS. Не больше двух знаков после запятой, иначе 400.
  • timeoutInMin — SLA выплаты: сколько минут выплата ждёт депозиты. Не меньше минимума платформы (30 мин по умолчанию), иначе 403 MATCHING_TIMEOUT_OUT_OF_LIMITS.
  • Метод выводится из реквизита: sbpNumber → sbp_invoice, иначе cardNumber → c2c_invoice. Ни одного → 400 MISSED_PARAMS.
  • При создании с баланса сразу списывается тело и ставка мэтчинга по курсу на момент создания. Неиспользованное вернётся при закрытии.
  • На один и тот же реквизит может быть открыто несколько выплат игрока. Каждая создаётся со своим merchantTransactionId и отдельным резервом; ограничения баланса и общего открытого объёма продолжают действовать.
  • Повтор с тем же merchantTransactionId → 409 MATCHING_DUPLICATE_MERCHANT_TRANSACTION_ID, в теле — существующая выплата; второго списания нет. Ключ общий для выплат мэтчинга и обычных выплат мерчанта: обычная выплата (isMatching=false) с ключом существующей выплаты мэтчинга и выплата мэтчинга с ключом существующей обычной выплаты получают тот же 409, в теле — та выплата, что уже есть.
  • Ключи депозитов и выплат проверяются раздельно: депозит и выплату с одним merchantTransactionId платформа примет обе. Но GET /payment/merchant/{id} по такому ключу вернёт выплату, а депозит по ключу будет не найти. Если вы и принимаете депозиты, и делаете выплаты, задавайте им разные ключи (например, с префиксом in- / out-); депозит по-прежнему доступен по id — GET /payment/{id}.

Ответ — один формат для создания, GET и callback-а

Section titled “Ответ — один формат для создания, GET и callback-а”
{
"id": "9ee42dff-83ec-4a90-8dfb-1fc74d3afbe4",
"merchantTransactionId": "payout-100500",
"status": "active",
"type": "pay-out",
"method": "sbp_invoice",
"currency": "RUB",
"amount": 15500,
"factFiatAmount": 10000,
"crypto": "167567567",
"fee": "1675675",
"rate": 92.5,
"requisite": { "sbpNumber": "79991234567", "bank": "SBER", "ownerName": "Иван Иванов" },
"isMatching": true,
"slaDeadlineAt": "2026-09-28T12:00:00.000Z",
"closingStartedAt": null,
"closeReason": null,
"cancelReason": null,
"adjustedAt": null,
"refundedCrypto": "0",
"eventSeq": 0,
"fills": [
{ "fillId": "5b1c7a8e-3f0a-4c55-9d8e-0a1b2c3d4e5f", "amount": 5000, "status": "success", "settledAt": "2026-09-28T10:20:00.000Z", "source": "pay_in", "reversedAt": null },
{ "fillId": "0d2f41c9-8b7e-4a0e-9b1d-3c5e7f9a1b2c", "amount": 5000, "status": "success", "settledAt": "2026-09-28T10:41:00.000Z", "source": "pay_in", "reversedAt": null },
{ "fillId": "6a8c0e2f-4b6d-4f1a-8c3e-5d7f9b1d3f5a", "amount": 5500, "status": "in_progress", "settledAt": null, "source": "pay_in", "reversedAt": null }
],
"createdAt": "2026-09-28T10:00:00.000Z",
"updatedAt": "2026-09-28T10:41:00.000Z",
"callbackUrl": "https://merchant.example.com/callback"
}
ПолеСмысл
statusactive — выплата принимает депозиты или закрывается; success — оплачено ровно amount; success_changed — закрыта с другой суммой (SLA, закрытие на оплаченном, непогашаемый остаток, корректировка); canceled — ничего не оплачено
amount / factFiatAmountсумма выплаты / фактически оплачено получателю = сумма частей в success
crypto, fee, rateсписано при создании: тело и ставка мэтчинга (минимальные единицы USDT, строкой), курс создания
refundedCryptoвозвращено на баланс (все возвраты по выплате, минимальные единицы USDT)
slaDeadlineAtпредельный срок ожидания депозитов; полная оплата или непогашаемый остаток могут завершить выплату раньше
closingStartedAtначало закрытия (SLA, «Закрыть на оплаченном» или закрытие оператором); при автоматическом финале может остаться null
closeReasonfilled · sla_recalculated · unfillable_tail · admin_recalculated · merchant_recalculated · externally_reduced · adjusted_after_close
cancelReasonsla_expired_no_fill · admin · merchant · externally_reduced
adjustedAtпоследняя корректировка после закрытия
eventSeqномер последнего callback-события (0 — callback-ов ещё не было)
fills[]части выплаты (ниже)
  • fillId — непрозрачный идентификатор части; id сделок игроков и трейдеров вы не получаете.
  • source: pay_in — депозит игрока, fallback — выплата трейдером платформы (если депозитов не хватило к таймеру fallback).
  • status: in_progress — депозит оплачивается / проверяется или трейдер выполняет выплату; success — часть оплачена; reversed — часть была успешной и аннулирована (чек признан фейком или выплата трейдера отменена по претензии).
  • reversedAt — момент аннулирования: заполнен только у reversed, у остальных частей null (поле есть у каждой части).
  • amount части с source = fallback платформа может уточнить после проверки оплаты. После финала это приходит корректировкой (ниже).
  • Отменённые без успеха части в fills[] не попадают. «Не оплачено и не в работе» = amount − Σ fills[].amount.
  • Сверка денег по выплате: crypto + fee − refundedCrypto = Σ успешных частей × (1 + ставка мэтчинга) / rate (± единицы округления).
  • Только финал (success / success_changed / canceled) и каждая корректировка после финала. При создании и на отдельные части callback-ов нет — части видны в GET.
  • Корректировка после финала — success_changed с closeReason = adjusted_after_close и новым adjustedAt. factFiatAmount, refundedCrypto и fills[] в теле уже пересчитаны: часть может перейти в reversed (с reversedAt) или получить уточнённую amount. Обычно factFiatAmount уменьшается, а refundedCrypto растёт — разница вернулась на баланс. Если платформа после финала подтвердит оплату части, factFiatAmount вырастет, сумма этой части повторно спишется с баланса, а refundedCrypto уменьшится. Сверяйте выплату по последнему callback-у и формуле сверки выше.
  • Тело — снимок текущего состояния в момент отправки, поэтому success и следом success_changed могут прийти с одинаковым телом. Обрабатывайте callback-и идемпотентно по паре (id, eventSeq) — eventSeq растёт с каждым событием выплаты.
  • Повторная отправка по запросу в поддержку приходит новым событием (следующий eventSeq) с текущим состоянием.

Автоматическое закрытие малого остатка

Section titled “Автоматическое закрытие малого остатка”

Если все разрешённые чеки использованы, часть выплаты оплачена, а положительный остаток меньше текущего минимального размера fallback, платформа автоматически закрывает выплату на оплаченном до SLA: status = success_changed, closeReason = unfillable_tail. Это правило действует при включённых мэтчинге и fallback; остаток, равный минимуму fallback, под него не попадает. Открытый разбор, ожидающая претензия о поздней оплате, чек в работе или hold, взятый либо карантинный fallback откладывают такой финал. Невзятые fallback сначала отменяются; если часть успели взять, система ждёт её завершения.

amount сохраняет исходную сумму, factFiatAmount содержит только оплаченные части. Например, выплата на 4 750 ₽ с четырьмя успешными чеками на 4 603 ₽ и остатком 147 ₽ при минимуме fallback 1 000 ₽ завершится с amount = 4750, factFiatAmount = 4603 и closeReason = unfillable_tail. Неиспользованный USDT-резерв возвращается на внутренний баланс мерчанта и учитывается в refundedCrypto; это не банковская довыплата 147 ₽ получателю. Финал приходит штатным callback-ом; суммы уже оплаченных частей не увеличиваются.

Контракт подготовлен локально 14.10.2026; этот раздел не подтверждает production rollout.

POST /payment/pay-out/{id}/close
X-Secure-Key: <api-key>

После вызова новые депозиты на выплату не принимаются, части в работе доживают, неиспользованный остаток вернётся на баланс. Вернуть выплату в работу нельзя. Ответ 200 — выплата с заполненным closingStartedAt; повтор — 200 с текущим состоянием. Финал и callback приходят после закрытия: success_changed с closeReason = merchant_recalculated, success (если успели оплатить всё) или canceled с cancelReason = merchant (если ничего не оплачено). Чужая или несуществующая выплата — 404.

POST /payment/pay-out/{id}/cancel отменяет выплату, только если в ней ничего не оплачено и ничего не в работе, иначе 409 MATCHING_PAYOUT_NOT_CANCELABLE (закройте на оплаченном). Успех — 200, canceled, cancelReason = merchant, списанное вернётся на баланс; повтор по отменённой выплате — 200 с текущим состоянием.

Fallback-выплаты трейдеров

Section titled “Fallback-выплаты трейдеров”

Выплаты трейдерам, которые платформа создаёт для части суммы, вы не видите нигде, кроме fills[] (source = fallback): их нет в списке GET /payment, GET /payment/{id} и поиск по merchantTransactionId отвечают 404, callback-ов по ним нет, отмена и спор по ним → 403 MATCHING_FALLBACK_MANAGED_BY_PLATFORM. Если получатель не получил деньги по успешной части — нажмите «Получатель не получил деньги» у части в кабинете или напишите в поддержку.


POST /payment/pay-in с method: "sbp_invoice" или "c2c_invoice". Новых полей в запросе нет; counteragent для invoice-методов игнорируется.

POST /payment/pay-in
X-Secure-Key: <api-key>
Content-Type: application/json
{
"amount": 5000,
"currency": "RUB",
"method": "sbp_invoice",
"merchantTransactionId": "deposit-777",
"callbackUrl": "https://merchant.example.com/callback",
"errorUrl": "https://merchant.example.com/fail",
"payerInfo": { "userId": "player-1", "ip": "203.0.113.10", "userAgent": "Mozilla/5.0", "fingerprint": "…", "registeredAt": "1728388185326" }
}

Возможные ответы:

ОтветКогдаЧто делать
status: "active" с реквизитомсумма игрока подходитпоказать реквизит, как обычно
status: "pending" с блоком amountOptions, requisite: nullнужен выбор суммыпоказать выбор (свой экран через API) или отправить игрока на paymentPageUrl
сделка с методом sbp / c2c (классика)мэтчинг не подошёл, у вас включена классикаобычная сделка, старый контракт
403ничего не подошло, классика выключенакод в таблице ошибок ниже

Если у вас выключен «Выбор суммы» (переключатель у мерчанта, включает платформа по запросу), новые invoice-запросы не создают этап выбора: только active на запрошенную сумму, классика или 403. Уже открытые сессии выбора продолжают обрабатываться; отключение их не отменяет. Идемпотентный повтор ранее созданной заявки возвращает её текущее состояние, в том числе pending.

  • callbackUrl — адрес http:// или https://; другой адрес → 400.
  • merchantTransactionId обязателен, не длиннее 255 символов и уникален среди ваших invoice-запросов. Повтор с тем же amount, currency, method → 200 и текущее состояние той же сделки (в том числе canceled или классика); с другим телом → 409 MATCHING_DUPLICATE_MERCHANT_TRANSACTION_ID. Остальные поля (payerInfo, callbackUrl …) не сравниваются. Два одинаковых запроса одновременно дают одну сделку.
  • amount до выбора — запрошенная сумма, после выбора — выбранная. В запросе — не больше двух знаков после запятой, иначе 400.

Поля ответа invoice-депозита сверх обычных: initialAmount (сумма, которую запросил игрок), amountChanged (итоговая отличается от запрошенной), cancelDetail (amount_offer_declined · amount_offer_expired · no_matching_capacity), amountOptions (этап выбора, иначе null), requisiteIssuedAt (выдача реквизита — окно оплаты считается от неё), creditReversedAt и creditReversedCrypto (сторно зачисления, см. ниже; без сторно — null). Полей matchingId, matchingTransactionId, matchingTradeType у invoice-депозитов нет. В pending: requisite, paymentLifetime, rate, crypto — null, fee — "0" (ещё не посчитано, см. ниже), paymentPageUrl заполнен.

Суммы до выдачи реквизита

Section titled “Суммы до выдачи реквизита”

Курс, сумму в USDT и комиссию платформа считает в момент выдачи реквизита (requisiteIssuedAt). Пока реквизит не выдан, этих сумм ещё нет:

СостояниеrequisiteIssuedAtcryptofeeratepaymentLifetime
pending — этап выбора суммыnullnull"0"nullnull
canceled до выдачи реквизита (заполнен cancelDetail)null"0""0"nullокно оплаты из настроек
реквизит выдан (active и дальше)время выдачисуммакомиссиякурсокно оплаты

Фрагмент ответа в pending:

{
"id": "3b0c8f0e-6a55-4bb1-9c41-2d8c6f3a9e10",
"status": "pending",
"amount": 5000,
"crypto": null,
"rate": null,
"fee": "0",
"requisite": null,
"paymentLifetime": null,
"requisiteIssuedAt": null,
"cancelDetail": null,
"amountOptions": {
"requestedAmount": 5000,
"ownAmountAvailable": false,
"offers": [{ "offerId": "9c1e7b20d4f6", "amount": 5500, "diff": 500, "recommended": true }],
"insistAvailable": true,
"round": 1,
"choiceExpiresAt": "2026-09-28T10:09:30.000Z",
"sessionStatus": "open"
}
}

Фрагмент ответа после отмены на этапе выбора (игрок отказался от вариантов):

{
"id": "3b0c8f0e-6a55-4bb1-9c41-2d8c6f3a9e10",
"status": "canceled",
"amount": 5000,
"crypto": "0",
"rate": null,
"fee": "0",
"requisite": null,
"paymentLifetime": 15,
"requisiteIssuedAt": null,
"cancelDetail": "amount_offer_declined",
"amountOptions": null
}
"amountOptions": {
"requestedAmount": 5000,
"ownAmountAvailable": true,
"offers": [
{ "offerId": "3fa85f64a1b2", "amount": 4800, "diff": -200, "recommended": false },
{ "offerId": "9c1e7b20d4f6", "amount": 5500, "diff": 500, "recommended": true }
],
"insistAvailable": false,
"round": 1,
"choiceExpiresAt": "2026-09-28T10:09:30.000Z",
"sessionStatus": "open"
}
  • ownAmountAvailable: true — сумму игрока можно выбрать сразу («Оплатить 5 000 ₽»), варианты — рекомендации.
  • ownAmountAvailable: false — сумма игрока сейчас не принимается, выбрать можно только вариант; insistAvailable: true — можно один раз «Оставить мою сумму» (платформа поищет получателя точно под неё).
  • offers — до 3 вариантов по возрастанию суммы; recommended — подтвердится быстрее. offerId непрозрачный и стабилен в раунде.
  • round: 2 — «Оставить мою сумму» не нашло получателя, остались только варианты.
  • choiceExpiresAt — последний момент, когда выбор принимается. Выбора не было и ownAmountAvailable: true — платформа сама оставит сумму игрока, когда получатель под неё ещё есть: депозит станет active с реквизитом на исходную сумму, придёт callback active. Иначе (и всегда при ownAmountAvailable: false) депозит отменится с cancelDetail = amount_offer_expired.
  • sessionStatus: open · accepted · declined · expired.
  1. Свой экран. Покажите amountOptions и вызовите эндпоинты выбора (ниже). Статус и блок можно перечитывать через GET /payment/pay-in/{id}/amount-options или GET /payment/{id}.
  2. Редирект на paymentPageUrl. Платёжная страница покажет выбор, реквизит и приём чека. Итог придёт callback-ом.

Выбор сделанный на странице и через API меняет одну и ту же сессию: кто первый выбрал, тот и выбрал.

Надёжный переход от суммы к оплате

Section titled “Надёжный переход от суммы к оплате”

Сначала определите источник кнопок. GET /payment/matching/demand — предварительная подсказка пула, до создания депозита: в ней нет tradeId и offerId. Клик выбирает сумму для POST /payment/pay-in. amountOptions — предложения уже созданного депозита: клик подтверждается через POST /payment/pay-in/{id}/amount-options/accept. Эти два списка нельзя смешивать.

Ни показанная предварительная сумма, ни предложение с offerId не резервируют ёмкость. При создании и при accept платформа проверяет её заново. GET amount-options читает сохранённый снимок; частый polling не пересчитывает доступность.

Пример создания после выбора предварительной суммы (заголовок X-Secure-Key передаётся только вашим сервером):

POST /payment/pay-in
Content-Type: application/json
{"amount":5000,"currency":"RUB","method":"sbp_invoice","merchantTransactionId":"deposit-selection-example-1"}

Сохраните merchantTransactionId до запроса. После потери ответа повторяйте тот же запрос с тем же ключом и параметрами: первое создание отвечает 201, идемпотентный повтор invoice — 200 с той же сделкой. Не генерируйте новый ключ на каждый клик. Проверяйте также status и method: invoice может пойти через обычный маршрут, описанный выше.

Ниже — сокращённые примеры ответов с синтетическими ID; не перечисленные поля остаются по основному контракту.

{
"id": "3b0c8f0e-6a55-4bb1-9c41-2d8c6f3a9e10",
"status": "pending",
"paymentPageUrl": "https://pay.example.test/3b0c8f0e-6a55-4bb1-9c41-2d8c6f3a9e10",
"requisite": null,
"amountOptions": {
"requestedAmount": 5000,
"ownAmountAvailable": true,
"offers": [{"offerId":"9c1e7b20d4f6","amount":5500,"diff":500,"recommended":true}],
"insistAvailable": false,
"round": 1,
"choiceExpiresAt": "2026-09-29T20:09:30.000Z",
"sessionStatus": "open"
}
}

pending — уже созданная заявка. Можно сразу перейти на возвращённый paymentPageUrl: Ohlapay сама покажет выбор или ожидание. Не ждите callback pending — его нет. Если выбор находится на вашем сайте, храните вместе id, offerId, сумму, choiceExpiresAt; блокируйте повторную отправку, пока запрос выполняется. Срок проверяйте с учётом серверного времени, затем полагайтесь на результат API. До отправки покажите игроку итоговую сумму.

В серверном адаптере маршрута с платёжной страницей обработайте успешные pending и active как создание одной заявки: сохраните её id и передайте браузеру переход на paymentPageUrl в принятом у вас формате. requisite: null в pending — штатное состояние, а не ошибка создания. Не обращайтесь к полям реквизита до проверки статуса и не ждите автовыбора по таймеру, чтобы разрешить переход. Принятый callback active сам по себе не означает, что браузер открыл платёжную страницу.

POST /payment/pay-in/3b0c8f0e-6a55-4bb1-9c41-2d8c6f3a9e10/amount-options/accept
Content-Type: application/json
{"offerId":"9c1e7b20d4f6"}

Для исходной суммы передайте {"ownAmount":true} вместо offerId, только если это предлагается сессией. Не передавайте оба поля. Один offerId относится к одной сделке; ранее выданный этой сессией вариант может быть принят, если сумма всё ещё доступна.

Пример значимых полей успешного ответа 200 (реквизит намеренно не приведён):

{
"id": "3b0c8f0e-6a55-4bb1-9c41-2d8c6f3a9e10",
"status": "active",
"amount": 5500,
"initialAmount": 5000,
"amountChanged": true,
"amountOptions": null,
"paymentPageUrl": "https://pay.example.test/3b0c8f0e-6a55-4bb1-9c41-2d8c6f3a9e10"
}

Обрабатывайте ответы единым автоматом состояния:

Ответ / состояниеДействие интегратора
pending, открытые варианты, срок не истёкПокажите выбор или перейдите на paymentPageUrl.
pending без вариантов / sessionStatus=acceptedПокажите ожидание, ограничьте длительность запросов и перечитывайте GET /payment/{id}; не создавайте второй депозит.
200, актуальный activeПокажите итоговую amount и перейдите на возвращённый paymentPageUrl. Не продолжайте показывать старые кнопки.
409 MATCHING_AMOUNT_OPTION_UNAVAILABLEПроверьте trade.id, примените trade.status; при pending замените варианты из trade.amountOptions и объясните, что сумму заняли. При canceled покажите завершение. Не повторяйте старый вариант автоматически после 409.
409 MATCHING_AMOUNT_SELECTION_CLOSEDПримените состояние той же сделки из trade или перечитайте GET /payment/{id}, если тела нет. active может означать автовыбор; canceled — закрытие. Не считайте любой CLOSED отменой.
Истёк choiceExpiresAt, но сервер ещё pendingОтключите старый выбор и проверьте состояние. До финального действия таймера возможен промежуток; срок выбора не равен сроку оплаты.
Сеть / timeout, исход accept неизвестенСначала получите состояние той же сделки. Повтор допустим с тем же id и тем же выбором: успешный accept идемпотентен. Другой выбор после принятого даст CLOSED.
GET после успешного accept не удалсяПовторяйте GET; показывайте ошибку проверки состояния и действие «Повторить». Новую сделку не создавайте.
400, 401, 403, 404, 5xxПокажите понятную ошибку и восстановление/обращение в поддержку; завершите индикатор загрузки. Не скрывайте ошибку в console.
checking, checking_manually, dispute, success, success_changed, canceledСледуйте текущему статусу и основному контракту; не предлагайте новую оплату по старому выбору.

Для закрытой сессии без доступного тела разрешён такой ответ:

{"statusCode":409,"message":"MATCHING_AMOUNT_SELECTION_CLOSED","trade":null}

Если после 200 active браузер остаётся на вашем экране, покажите явную ссылку «Перейти к оплате» с тем же paymentPageUrl. Не открывайте popup только после асинхронного запроса: браузер может его заблокировать. Навигация в текущей вкладке или обычная ссылка должны оставаться доступны. Проверяйте разрешённый адрес, не собирайте URL из offerId и не подставляйте домен наугад.

Для диагностики отдельно отмечайте: источник списка (demand / amountOptions), показ, клик, отправку accept, HTTP status и код ошибки, состояние ответа, попытку навигации и результат на принимающей странице. Используйте id, свой идентификатор попытки, время UTC и X-REQ-ID / X-CORRELATION-ID ответа. Не сохраняйте ключи, cookies, actionToken, реквизиты, персональные данные или сырой HAR. Сигнал redirect_started означает попытку навигации, а не посещение. Выдача реквизита и автовыбор по таймеру не доказывают клик.

Сначала проверьте серверный адаптер отдельно: ответ создания 201 pending с requisite: null, заполненными paymentPageUrl и amountOptions должен вернуть браузеру успешный результат с переходом; 201 active должен сохранить тот же путь навигации. Идемпотентный повтор 200 pending должен сохранить ID заявки, а не создавать новую. Затем проверьте в браузере, что каждый из этих результатов действительно открывает переданный URL или оставляет рабочую ссылку на него.

Контрактная проверка собственного экрана выбора: замокайте последовательно pending → 200 active, pending → 409 со свежими вариантами, 409 CLOSED → GET active, 409 CLOSED → GET canceled, 409 trade:null → GET 503, потерянный ответ accept и успешный accept с сорванной навигацией. Проверьте видимый результат каждого клика, один ключ создания, отсутствие параллельных accept и рабочую ссылку на оплату. Устаревший снимок сам по себе не доказывает причину неработающей кнопки.

  • На pending callback-а нет — это состояние вы получили в ответе на создание.
  • active — после выдачи реквизита (сразу, после выбора или по истечении выбора со своей суммой игрока), с initialAmount и amountChanged.
  • success / success_changed — как у обычного депозита; canceled — с cancelDetail, если отменён на этапе выбора.
  • canceled после success — сторно зачисления: заполнены creditReversedAt и creditReversedCrypto (раздел «Сторно зачисления» ниже).
  • В callback-ах amountOptions всегда null.

Каждый callback — отдельное событие. Тело — состояние депозита в момент события, а не в момент доставки. В теле есть eventSeq — номер события по депозиту: 1, 2, 3 … В ответах API поля eventSeq нет.

  • Повторная доставка (ваш сервер не ответил 200) приходит с тем же eventSeq и тем же телом.
  • События могут прийти не по порядку: повтор раннего события после сбоя доставки может прийти позже следующего.
  • Обрабатывайте callback-и идемпотентно по паре (id, eventSeq): событие уже обработано — ответьте 200 и ничего не делайте.
  • Актуальное состояние — у события с наибольшим eventSeq. Событие с eventSeq меньше уже обработанного не применяйте, но ответьте 200.
  • Повторная отправка по запросу в поддержку приходит новым событием (следующий eventSeq) с текущим состоянием.

Пример: игрок получил реквизит, и через секунду вы отменили депозит. Придут два callback-а — active, затем canceled:

{
"id": "3b0c8f0e-6a55-4bb1-9c41-2d8c6f3a9e10",
"merchantTransactionId": "deposit-777",
"status": "active",
"amount": 5000,
"crypto": "54054054",
"fee": "1081081",
"requisite": { "bank": "SBER", "sbpNumber": "79990001122", "ownerName": "Иван И." },
"requisiteIssuedAt": "2026-09-28T10:02:00.000Z",
"cancelDetail": null,
"amountOptions": null,
"eventSeq": 1
}
{
"id": "3b0c8f0e-6a55-4bb1-9c41-2d8c6f3a9e10",
"merchantTransactionId": "deposit-777",
"status": "canceled",
"amount": 5000,
"crypto": "54054054",
"fee": "1081081",
"requisite": { "bank": "SBER", "sbpNumber": "79990001122", "ownerName": "Иван И." },
"requisiteIssuedAt": "2026-09-28T10:02:00.000Z",
"cancelDetail": null,
"amountOptions": null,
"eventSeq": 2
}

Здесь показаны не все поля: остальные — как в ответе GET /payment/{id}.

Сторно зачисления (чек признан фейком)

Section titled “Сторно зачисления (чек признан фейком)”

Если оператор платформы признал чек депозита фейком, а вы подтвердили, что игрок не использовал средства, платформа отменяет депозит уже после успеха:

  • с вашего баланса списывается зачисление по депозиту — crypto − fee (запись журнала баланса MATCHING_PAY_IN_CREDIT_REVERSED);
  • депозит получает status: "canceled", creditReversedAt (время списания) и creditReversedCrypto (списанная сумма, минимальные единицы USDT) — в callback-е, GET /payment/{id}, GET /payment/merchant/{id} и в списке GET /payment;
  • приходит callback canceled со следующим eventSeq. Повторного success не бывает.

Почему canceled, а не success_changed: success_changed — успешный статус (спор закрыт, сумма пересчитана), интеграции зачисляют по нему игроку деньги и начисляют бонусы. canceled значит «депозит не состоялся» — так же платформа отменяет успешный депозит и у обычных сделок. Новый статус не вводили, чтобы не сломать разбор ответов. Отличить сторно от обычной отмены можно по creditReversedAt: у сторно оно заполнено.

Что сделать: получили canceled с заполненным creditReversedAt по депозиту, который вы уже зачислили, — спишите зачисление у игрока. Если вы ответили платформе, что игрок уже использовал средства, сторно не будет: депозит остаётся success, callback-а нет.

Пример callback-а сторно:

{
"id": "3b0c8f0e-6a55-4bb1-9c41-2d8c6f3a9e10",
"merchantTransactionId": "deposit-777",
"status": "canceled",
"amount": 5000,
"crypto": "54054054",
"fee": "1081081",
"requisiteIssuedAt": "2026-09-28T10:02:00.000Z",
"cancelDetail": null,
"creditReversedAt": "2026-09-28T12:05:00.000Z",
"creditReversedCrypto": "52972973",
"eventSeq": 3
}

До сторно по этому депозиту приходили active (eventSeq 1) и success (eventSeq 2) с creditReversedAt: null.

POST /payment/pay-in/{id}/dispute (тело: uploadMetaKey чека, по желанию amount — не больше двух знаков после запятой) и POST /payment/pay-in/{id}/dispute/cancel работают как у обычных сделок и отвечают 200 с текущим состоянием депозита в формате этой страницы.

  • Депозит active — спор отложенный, как у обычных сделок: он начнётся, если сделка отменится по таймауту.
  • Депозит canceled (игрок оплатил после отмены) — спор открывается сразу, если сумма ещё не закрыта другим платежом: 200, статус dispute.
  • Сумма уже закрыта другим платежом — 409 MATCHING_LATE_PAYMENT_CAPACITY_TAKEN с caseId. Депозит остаётся canceled, спор передан оператору платформы. Повтор спора вернёт тот же caseId. Если оператор подтвердит оплату, придёт callback с итоговым статусом.
  • Выплата, которую закрывал депозит, уже закрыта — 409 MATCHING_RESERVE_RELEASED с caseId, спор тоже у оператора.
  • Депозит на этапе выбора суммы (pending, без реквизита) оспаривать нечего — 404.
  • Чек депозита уже на проверке или в споре (checking, checking_manually, dispute) — 409 MATCHING_CHECK_UNDER_REVIEW, в trade — текущее состояние депозита. Дождитесь решения по чеку: итог придёт callback-ом.

Эндпоинты выбора суммы

Section titled “Эндпоинты выбора суммы”

Все — с X-Secure-Key, только по своим сделкам (чужая → 404).

ЭндпоинтЧто делаетУспех
GET /payment/pay-in/{id}/amount-optionsтекущий раунд без пересчёта200 + блок; после выбора / отмены → 409 MATCHING_AMOUNT_SELECTION_CLOSED
POST /payment/pay-in/{id}/amount-options/accept {"offerId": "…"} или {"ownAmount": true}выбрать вариант или свою сумму200, сделка active с реквизитом
POST /payment/pay-in/{id}/amount-options/keep-amount«Оставить мою сумму»200: active или pending раунда 2 со свежими вариантами (или canceled, если вариантов не осталось)
POST /payment/pay-in/{id}/cancelотказ на этапе выбора200, canceled, cancelDetail = amount_offer_declined
  • Варианты зафиксированы на раунд: GET их не пересчитывает, меняются они только после 409, keep-amount или смены раунда.
  • Повтор успешного accept / keep-amount → 200 с текущим состоянием; повтор cancel по отменённой сделке → 200 с текущим состоянием. Отмена обычных (не invoice) сделок отвечает, как раньше, 201.
  • accept без offerId и ownAmount или с обоими → 400. {"ownAmount": true} при ownAmountAvailable: false → 409 MATCHING_AMOUNT_OPTION_UNAVAILABLE.
  • keep-amount в сессии, где «Оставить» не предлагали, → 409 MATCHING_KEEP_AMOUNT_UNAVAILABLE.
  • Отмена депозита, чек которого уже на проверке или в споре (checking, checking_manually, dispute), → 409 MATCHING_CHECK_UNDER_REVIEW.

Пример гонки: вариант заняли

Section titled “Пример гонки: вариант заняли”
POST /payment/pay-in/3b0c8f0e-6a55-4bb1-9c41-2d8c6f3a9e10/amount-options/accept
{"offerId": "9c1e7b20d4f6"}
HTTP/1.1 409 Conflict
{
"statusCode": 409,
"message": "MATCHING_AMOUNT_OPTION_UNAVAILABLE",
"trade": {
"id": "3b0c8f0e-6a55-4bb1-9c41-2d8c6f3a9e10",
"status": "pending",
"amount": 5000,
"amountOptions": {
"requestedAmount": 5000,
"ownAmountAvailable": false,
"offers": [{ "offerId": "e41a0c93b7d2", "amount": 6000, "diff": 1000, "recommended": true }],
"insistAvailable": true,
"round": 1,
"choiceExpiresAt": "2026-09-28T10:09:30.000Z",
"sessionStatus": "open"
}
}
}

Покажите игроку свежие варианты из trade.amountOptions. Если trade.status = "canceled" — вариантов больше нет (cancelDetail = no_matching_capacity).


Все ошибки 409 мэтчинга — одно тело: {"statusCode": 409, "message": "<код>", "trade": {…}}, где trade — текущее состояние той же сделки или выплаты в формате этой страницы (для спора — сделки из пути запроса; null, только если сделку не удалось найти). У MATCHING_LATE_PAYMENT_CAPACITY_TAKEN и у MATCHING_RESERVE_RELEASED в ответ на спор в теле ещё caseId. Остальные ошибки — как везде: {"statusCode", "message"}. Схема MatchingConflictResponseDto в OpenAPI перечисляет все коды 409 мэтчинга, включая коды админки платформы (MATCHING_USE_DISPUTES_SECTION, MATCHING_CASE_ALREADY_RESOLVED, MATCHING_CLAIM_ALREADY_OPEN, MATCHING_CALLBACK_NOT_FINAL, MATCHING_FALLBACK_RELEASE_UNAVAILABLE), — merchant API их не возвращает.

КодHTTPГдеСмысл
MATCHING_DISABLED403pay-out; pay-in при выключенной классикемэтчинг выключен на платформе
MATCHING_PAYOUT_DISABLED403pay-outвыплаты мэтчинга у вас выключены
MATCHING_PAYIN_DISABLED403pay-inдепозиты мэтчинга у вас выключены, классика тоже
MATCHING_CURRENCY_NOT_SUPPORTED403pay-out; pay-inвалюта не поддерживается мэтчингом (сейчас только RUB)
MATCHING_TIMEOUT_OUT_OF_LIMITS403pay-outtimeoutInMin меньше минимального SLA
MATCHING_AMOUNT_OUT_OF_LIMITS403pay-outсумма вне лимитов выплаты
MATCHING_OPEN_VOLUME_LIMIT_REACHED403pay-outпревышен лимит открытых выплат
MATCHING_BALANCE_LIMIT_EXCEEDED403pay-outне хватает баланса с учётом лимита овердрафта
MISSED_PARAMS400pay-outв реквизите нет ни sbpNumber, ни cardNumber
NO_REQUISITE_FOUND_FOR_THIS_AMOUNT403pay-inничего не подошло, классика выключена
INTEREST_RATE_NOT_FOUND_FOR_METHOD_OR_CURRENCY403pay-inнет ставки на invoice-метод, классика выключена
MATCHING_DUPLICATE_MERCHANT_TRANSACTION_ID409pay-out; pay-inтот же merchantTransactionId с другим телом
MATCHING_REQUISITE_ALREADY_OPEN409исторический кодОграничение одной открытой выплаты на реквизит снято; новые выплаты по этому основанию не отклоняются.
MATCHING_PAYOUT_NOT_CANCELABLE409отмена выплатыв выплате есть оплаченные части или части в работе
MATCHING_AMOUNT_OPTION_UNAVAILABLE409acceptвариант заняли / своя сумма недоступна; свежий блок — в trade.amountOptions
MATCHING_AMOUNT_SELECTION_CLOSED409amount-options, accept, keep-amountэтап выбора закончился
MATCHING_KEEP_AMOUNT_UNAVAILABLE409keep-amount«Оставить мою сумму» не предлагали
MATCHING_CHECK_UNDER_REVIEW409отмена депозита; POST /payment/pay-in/{id}/disputeчек на проверке или в споре
MATCHING_LATE_PAYMENT_CAPACITY_TAKEN409POST /payment/{type}/{id}/dispute по отменённому депозитусумма уже закрыта другим платежом, спор передан оператору (caseId)
MATCHING_RESERVE_RELEASED409POST /payment/{type}/{id}/dispute, …/dispute/cancelвыплата уже закрыта, спор передан оператору
MATCHING_LEGACY_TRADE409действия по архивным выплатамархивная выплата мэтчинга (создана до 26.09.2026)
MATCHING_FALLBACK_MANAGED_BY_PLATFORM403отмена / спор / закрытие fallbackfallback-выплатами управляет платформа
MATCHING_EXPORT_CONCURRENCY_LIMIT429XLSX-выписка в кабинетеуже идут два экспорта; повторите через Retry-After секунд

Других 429 у мэтчинга нет: POST /payment/pay-in и эндпоинты выбора не ограничиваются по частоте.


Коллекция проходит оба сценария сверху вниз:

  1. Выплата: создание → GET → закрытие на оплаченном → отмена пустой (на отдельной выплате).
  2. Депозит, свой экран: создание → варианты → выбор варианта (accept) → GET.
  3. Депозит, «Оставить мою сумму» → раунд 2 → выбор варианта.
  4. Депозит, редирект: создание → paymentPageUrl (дальше игрок на странице) → опрос GET.
  5. Отказ на этапе выбора (cancel).

Перед запуском заполните baseUrl, secureKey и callbackUrl; остальные переменные коллекция берёт из ответов.


Сейчас принимаем: подсказка для депозитов A

Section titled “Сейчас принимаем: подсказка для депозитов A”

GET /payment/matching/demand?method=sbp_invoice с заголовком X-Secure-Key показывает, какие суммы депозитов сейчас подходят общему пулу мэтчинга. method — sbp_invoice (по умолчанию) или c2c_invoice; подсказка пока только для RUB. Другой метод → 400. Без действующего ключа → 401, действуют обычные ограничения доступа merchant API, включая IP whitelist.

Пример: две выплаты принимают только точный остаток 3 000,01 ₽ каждая:

{
"currency": "RUB",
"method": "sbp_invoice",
"enabled": true,
"generatedAt": "2026-09-29T10:00:00.000Z",
"expiresAt": "2026-09-29T10:00:30.000Z",
"payoutCount": 2,
"capacity": 6000.02,
"buckets": [
{ "from": 1000, "to": 1500, "payoutCount": 0, "capacity": 0 },
{ "from": 1500, "to": 3000, "payoutCount": 0, "capacity": 0 },
{ "from": 3000, "to": 5000, "payoutCount": 2, "capacity": 6000.02 },
{ "from": 5000, "to": 10000, "payoutCount": 0, "capacity": 0 },
{ "from": 10000, "to": null, "payoutCount": 0, "capacity": 0 }
],
"exactAmounts": [{ "amount": 3000.01, "payoutCount": 2 }]
}

Все суммы — рубли, до двух знаков после запятой. generatedAt и expiresAt — время UTC в ISO 8601. Снимок пула кэшируется на 30 секунд; частый опрос не делает его свежее. Рекомендуем запрашивать раз в 30 секунд отдельно для каждого используемого метода.

  • buckets — пять диапазонов [from, to): нижняя граница включена, верхняя исключена; null означает отсутствие верхней границы. payoutCount — число выплат окна кандидатов, принимающих хотя бы одну сумму в диапазоне, а не любую сумму из него.
  • capacity корзины — сумма свободных остатков этих выплат. Одна выплата может попасть в несколько корзин: ёмкости и счётчики корзин нельзя складывать. Поля верхнего уровня capacity и payoutCount уже посчитаны без повторов. Ёмкость — текущий свободный остаток, а не гарантия, что весь он будет закрыт чеками выбранного диапазона или в пределах ваших лимитов.
  • exactAmounts — точные остатки, сгруппированные по сумме и отсортированные по возрастанию. Депозит на такую сумму закрывает свободный остаток; не округляйте копейки. Если у выплаты есть другие чеки в обработке, её окончательный статус дождётся их.
  • Учитываются только выплаты в том же окне кандидатов по сроку, что использует подбор, живая занятость денег и слотов, удержания, взятый fallback, ожидающие претензии, план чеков выплаты и ваши лимиты депозитов. Суммы разных методов не смешиваются. Подсказка не раскрывает реквизиты, идентификаторы выплат или мерчантов B.
  • enabled=false и нули означают выключенный движок/приём депозитов для вашего кабинета либо отсутствие RUB в пуле. enabled=true и нули означают, что в снимке нет подходящей ёмкости. Ошибка загрузки не заменяется пустым или просроченным снимком.

Используйте корзины для выбора потока подходящих депозитов, а exactAmounts — для предложения игроку точной суммы, если ваш сценарий допускает выбор суммы. Затем создавайте обычный POST /payment/pay-in с тем же invoice-методом. Подсказка не резервирует выплату: между чтением и созданием депозита ёмкость может измениться. Всегда обрабатывайте штатный ответ создания, этап выбора суммы и отказы по правилам интеграции выше. Не меняйте сумму уже созданного депозита по этой подсказке.

Та же карточка «Сейчас принимаем» доступна в разделе мэтчинга кабинета. В админской статистике она показывает общий пул с глобальными лимитами A, независимо от выбранного исторического периода и мерчанта. Исторический раздел статистики «Спрос» отражает уже полученные запросы и не заменяет эту подсказку.