История изменений (Changelog)
История изменений Syncra Merchant API: новые возможности, исправления, изменения контракта
История изменений (Changelog)
2026-10-21
[FEATURE] Двуязычная документация — английский становится основным языком
- Публичная дока Merchant API теперь двуязычна (канон Stripe): английский
живёт на корне сайта (
/api/merchant-api/...), русский — по адресу/ru/api/merchant-api/.... Каждая страница, OpenAPI-спецификация и интерактивный API Reference доступны на обоих языках; поиск, sitemap и hreflang-альтернанты — раздельные по локалям. - Скачиваемая OpenAPI-спека: английская —
/openapi.yaml, русская —/openapi.ru.yaml. Структуры идентичны (проверено паритет-чеком). - Исторические записи changelog (ниже точки переключения 2026-10) остаются на русском. Новые записи публикуются на обоих языках с этой даты.
В данном разделе публикуется официальный список изменений и обновлений Syncra Merchant API. Мы фиксируем все новые функции (features), исправления ошибок (bugfixes), объявления об устаревании (deprecations) и обратно-несовместимые изменения (breaking changes).
2026-09-28
[REMOVED] Диспуты / RDR-контур выведен из платформы
Контур карточных диспутов (chargebacks, Visa VROL / Ethoca RDR, авто-возвраты) выведен из эксплуатации по решению владельца. Контур существовал вне P2P money-модели: деньги refund-саги никогда не двигались в рамках ордеров.
- Все dispute-RPC отвергают запросы
FAILED_PRECONDITION(HTTP-эквивалент grpc-gateway —400): вебхук-ингестPOST /api/v1/p2p/disputes/webhook, ручное создание/чтение/листинг диспутов, representment, ручной резолв и CRUD RDR-правил отвечают «RDR refund contour decommissioned: … is no longer processed». Пути сохранены в контракте (additive-политика: удаление путей — breaking change), поведение — честный reject. - Валидация формата и tenant-scope выполняется до отказа. Внешний
вебхук, если всё же придёт, получает reject + ERROR-лог + алерт оператору
p2p.dispute.rejected_decommissioned(Telegram ERROR-канал; дедуп 30 минут поtenant + external_ref— ретраи отправителя в окне не создают шторм алертов). - Таблицы контура удалены (миграция
000054:p2p_disputes,p2p_dispute_rules,p2p_dispute_evidence) — данных контура больше не существует. - Апелляционный контур не затронут:
/appeals*и V1/v1/disputs/*работают как раньше — спорные сделки разбираются апелляциями (см. Диспуты и Апелляции). - Вебхук-контур сделок не изменился: транзитная пара
deal.refund_pending→deal.cancelledи V116→12работают как задокументировано 2026-09-27; вREFUND_PENDINGсделку переводят отклонённая апелляция, third-party-флаг или правило сверки — не RDR-правило.
Инструкция по миграции: удалите вызовы dispute-API из интеграций — диспуты платформой не поддерживаются; вопросы разбора спорных сделок — через апелляционный контур. Обработчикам вебхуков сделок изменений не требуется.
2026-09-27
[DOC] REFUND_PENDING — транзитная семантика и гарантированный исход (additive)
Уточнена семантика статуса REFUND_PENDING: это внутренний транзитный
статус на пути к терминальному CANCELLED, а не самостоятельный исход
и не сигнал к действию мерчанта. В P2P-модели платформы «возврат игроку»
не существует: платёж не состоялся, и сторонних денежных движений нет —
холды снимаются идемпотентно.
- Уточнение семантики:
REFUND_PENDINGвозникает при обнаружении third-party-признака (отклонённая апелляция, флаг третьей стороны) и автоматически финализируется вCANCELLEDв пределах нескольких минут (гарантированная авто-финализация ≤ 10 минут). Внешне это проявляется парой вебхуковdeal.refund_pending→deal.cancelled. - Из документации удалён «RefundDone-цикл» (переход «возврат исполнен →
сделка возвращается в
INITIALIZEDи ждёт оплату заново») — данный путь не реализован в рантайме и описывался ошибочно. - Вебхук
deal.refund_pendingостаётся без изменений (additive): событие, пейлоад и условия доставки прежние — трактуйте его как транзит-уведомление, итоговый статус всегда приходит следующим вебхукомdeal.cancelled. - V1-контур: последовательность вебхуков
16(Returned) →12(Cancelled) теперь гарантирована — ранее сделка могла остаться в16без финального статуса. Код16— транзит, не финал; расхождение GET-контура (32RollingBack) и вебхук-контура (16) на этой стадии сохраняется намеренно (см. реестр кодов V1).
Инструкция по миграции: действий не требуется. Обработчики, ожидавшие
повторного входа в рабочий цикл после REFUND_PENDING/16, такого
поведения больше не встретят (его не было и раньше); конечный исход —
CANCELLED/12.
2026-09-26
Bank routing: маршрутизация «метод + банк» (1click-кнопки) — additive
POST /deals/payin: новое опциональное полеtarget_bank— канонический код банка из справочника (GET /banks?method=...). Не передано — поведение байт-в-байт как раньше. Неизвестный/отключённый/непривязанный к методу код →400с перечнем допустимых (target_bank: not found: requested "alfa", allowed: ... (see GET /merchant/banks?method=...)).GET /banksстал каноническим справочником банков (semantic change, санкционирован):bank_nameтеперь несёт машинный код банка платформы (sber/alfa/tbank/...), а не слаг метода; новые параметры?method=(банки конкретного метода — источник 1click-кнопок) и?direction=PAY_IN|PAY_OUT; новые поляpayin_available/payout_available— доступность вычислена по живому покрытию (мёртвая кнопка невозможна). Каталог методов по-прежнему:GET /payment-methods.POST /deals/payout:target_bankпринимает канонический код платформы (валидация по справочнику в направлении выплаты; прежние свободные слаги продолжают работать). Пустой банк у строгого провайдера больше не вешает заявку UNASSIGNED: платформа деривирует банк из IBAN (TR, позиции 5–9) или выбирает детерминированно из маппингов провайдера.- Вебхук
banks.updated(V2-only): уведомление об изменении справочника банков (notification, not a data source — перечитайтеGET /banks). Пейлоад:{bank_code, status, methods[]}. - Статус методов в каталоге теперь отражает доступность именно вашего мерчанта: если метод выключен для мерчанта оператором платформы,
GET /payment-methodsотдаёт его со статусомDISABLED(а пейин/пейаут по нему, как и раньше, вернёт400с пояснением). Раньше такой метод светилсяACTIVE— общим статусом справочника тенанта. - Валидация IBAN на выплатах (bank-transfer-реквизиты): контрольная сумма ISO 13616 (mod-97) проверяется при создании выплаты — опечатка в реквизите ловится сразу
400invalid IBAN: checksum failed for TR99…8765 (ISO 13616 mod-97), а не банком получателя спустя минуты. Пробелы/дефисы/регистр нормализуются до проверки; карточные и телефонные реквизиты не затронуты. - Санитайзинг ошибок: из клиентских ошибок мерч-контура убраны внутренние референсы (TD-номера, имена провайдеров); ошибки неизвестного банка несут перечень допустимых кодов в обоих режимах адаптера.
- Replay-семантика идемпотентности: повтор с тем же
idempotency_keyвозвращает исходную сделку независимо от нового тела; для другого банка — новый ключ. [DOC]Терминология Stripe-grade: из документации убрана вертикальная лексика — «игрок» → «клиент», примеры брендов нейтральные (Aurora Store вместо Casino Royal); API-значения сегментов (FTD/STD/TRUSTED/VIP) сохранены как есть — это реальный контракт.
2026-09-17
[FEATURE] Промо-бонусы депозитов: fee_breakdown в deal.completed
На завершённых PayIn-сделках платформа или спонсор акции может начислить
промо-бонус поверх обычного нетто мерчанта. Вебхук deal.completed
(V2, PayIn) теперь несёт объект fee_breakdown — раскладку
USDT-эквивалента платежа по легам и отдельную промо-строку. Полный разбор —
Промо-бонусы депозитов.
- Новое поле
fee_breakdownвCallbackEventPayloadсобытияdeal.completed(PayIn):gross_usdt_cents,merchant_commission_usdt_cents,merchant_cut_usdt_cents,team_cut_usdt_cents,agent_cut_usdt_cents,provider_fee_usdt_cents,platform_fee_usdt_cents— все суммы int64-строками (USDT-центы). - Промо-строка
promo_bonus_usdt_cents,promo_id,promo_sponsor_typeприсутствует только когда промо применён; при пропуске (например, бюджет акции исчерпан) платёж проходит обычным путём — объект есть, промо-полей нет. - Комиссия мерчанта не меняется (считается от фактической суммы платежа); бонус приходит тем же назначением, что нетто, и сверяется отдельно.
- Изменение additive: остальные события, версии API и V1-конверт не
затронуты — V1-мерчи новых полей не получают (гейт по
api_version).
Инструкция по миграции: никаких действий не требуется. Обработчики, не
читающие fee_breakdown, работают как раньше; при желании начните сверять
поступление как merchant_cut_usdt_cents + promo_bonus_usdt_cents.
[CHANGED] Вебхук deal.completed: fee_breakdown — только ваша картина
Из объекта fee_breakdown в вебхуке deal.completed (PayIn, V2) удалены
внутренние ноги платформы — platform_fee_usdt_cents,
provider_fee_usdt_cents, team_cut_usdt_cents, agent_cut_usdt_cents:
детализация издержек платформы наружу не раскрывается (канон Stripe
Connect / Adyen — gross, комиссия клиента и его нетто; себестоимость
платформы остаётся внутренней). Состав объекта теперь:
gross_usdt_cents/merchant_commission_usdt_cents/merchant_cut_usdt_cents— всегда;- промо-строка
promo_bonus_usdt_cents,promo_id,promo_sponsor_type— только при применённом промо.
Инструкция по миграции: если вы парсили удалённые поля — уберите их
обработку; расчёт нетто не меняется (merchant_cut_usdt_cents уже содержит
ваш нетто).
2026-09-11
[FEATURE] PayIn: автоматическое зачисление — только при точном совпадении суммы
Платёж зачисляется автоматически, только если переведённая сумма точно совпадает с суммой сделки (сравнение в минорных единицах, до копейки). Ранее сверка допускала небольшие отклонения на округление, комиссии банков и курсовую разницу — теперь допуск отсутствует во всех контурах:
- Точное совпадение (
full) — единственный сценарий автоматического завершения сделки. - Недоплата (
partial) и переплата (overpaid) — платёж не считается совпавшим: автоматического завершения нет, статус фиксируется вamount_match_status, фактически полученная сумма — вreceived_amount. Сделка живёт до таймаута и завершается терминальным статусом, если оператор не вмешался (см. Изменение суммы и зачёт по факту). - По переплате не возникает никаких денежных проводок — излишек не зачисляется ни на один баланс. Сведения о таком платеже (сумма, время, источник, связанная сделка) фиксируются платформой и передаются на рассмотрение операторам; возврат или зачёт излишка оформляется вручную через апелляцию.
- Автозавершение «по фактически полученной сумме» не предусмотрено и не может быть включено. Поведение при точном совпадении и Retry-матрица не изменились.
Инструкция по миграции: действий не требуется. Убедитесь, что плательщик переводит сумму сделки целиком, без вычета комиссии из суммы перевода — иначе платёж не будет распознан как совпавший.
2026-09-11
[FEATURE] PayIn: режим Direct (H2H) — реквизиты получателя в ответе API
POST /api/v1/p2p/merchant/deals/payin получил необязательное поле mode:
"HOSTED" (умолчание, прежнее поведение) или "DIRECT" — режим без
редиректа на платёжную страницу: экран с реквизитами получателя отрисовывает
сам мерчант, а наша payform клиенту не показывается. Матчинг, эскроу,
комиссии и вебхуки от режима не зависят. Полный разбор — PayIn → Режим
Direct (H2H).
modeв запросе: необязателен, пусто =HOSTED; недопустимое значение →400 INVALID_ARGUMENT(«invalid mode ...: must be HOSTED, DIRECT or empty (default HOSTED)»), сделка не создаётся.requisitesв ответе создания иGET /deals/{deal_id}: для DIRECT-сделок СВОЕГО мерча — полное сообщениеDealRequisites:requisite_type(тип реквизита — то же имя и тот же словарь, что вrequisite_typesкаталога методов:BANK_CARD/E_WALLET/SBP/SIM/BANK_TRANSFER),card_number(полный PAN, только BANK_CARD),cardholder_name,bank_name,phone(E_WALLET/SBP/SIM),iban(только BANK_TRANSFER),qr_payload(резерв, всегда пусто),requisite_id(UUID),expires_at(RFC3339, равенpayment_timeout_at). Видимость — только пока сделка ждёт оплаты (ESCROW_LOCKED/PAYMENT_NOTIFIED); в терминальных статусах блок скрывается (PII-минимизация). Хостед-сделки:mode: "",requisites: null— обратная совместимость сохранена.- Одноразовость вместе со сделкой: реквизит выдан матчингом каскада
именно этой сделке (
requisite_idв ответе — идентификатор выданного реквизита); после истеченияexpires_atсделка терминальноEXPIRED— перевыдача = новая сделка с новымidempotency_key, отдельной ручки перевыдачи нет. - Подтверждение оплаты (переход
ESCROW_LOCKED → PAYMENT_NOTIFIED): рекомендуемая публичная ручкаPOST /checkout/{hash}/notify-payment({hash}— суффиксpayment_urlпосле/pay/; повторы идемпотентно коллапсируют, недопустимый переход статуса →409) и альтернативнаяPOST /orders/{order_id}/notify-payment(обязательныеdeal_hash+idempotency_key: тот же ключ →200replay, новый ключ по ужеPAYMENT_NOTIFIED→400 FAILED_PRECONDITION). - Антифрод: DIRECT-сделка помечается в антифрод-контуре платформы как идущая без клиентских сигналов нашей пейформы; для мерча это прозрачно — действий не требуется.
- Retry-матрица не меняется: новые коды не вводились —
400не ретраить,503 UNAVAILABLE(нет провайдера; сделка фиксируетсяEXPIRED) — ретраить с backoff, как раньше. - V1-адаптер: полей
mode/requisitesне получает (V1-конверт не расширяется) — DIRECT доступен только мерчантам V2; V1 deprecated.
Инструкция по миграции: не требуется — режим опционален, умолчание HOSTED
сохраняет прежний контракт. Чтобы включить DIRECT, передайте mode: "DIRECT",
отрисуйте requisites с таймером до expires_at и подтверждайте оплату
клиента через notify-payment.
[DOC] Сверка смежных страниц с режимом Direct
- Коды ошибок: уточнено разветвление
403/404при чужом ресурсе — сделка другого мерчанта вашего тенанта отвечает403и на чтенииGET /deals/{id}(несуществующий ID —404); тем же правилом защищён доступ к реквизитам DIRECT. - Callbacks: источник реквизитов для PayIn дополнен — помимо
публичного
GET /checkout/{hash}, тот же блок для DIRECT-сделок доступен мерчанту-владельцу в ответе создания иGET /deals/{deal_id}.
2026-09-09
[FIX] V1-адаптер приведён к легаси-спеке
Compat-адаптер /v1/* сведён с легаси-спекой (swagger_formatted.json):
все расхождения контракта закрыты. Изменения касаются только V1-контура,
V2 API не затронут.
- Крипто-вывод (
POST /v1/merchant/withdraw): обязательное поле спеки —wallet(алиасaddressпринят; при заполнении обоих выигрываетwallet). Адрес валидируется по формату mainnet Base58Check TRON (T+ 33 base58-символа), иначе400 INVALID_ARGUMENT. Ручка идемпотентна поrequestId: повтор с тем жеrequestIdвозвращает существующий вывод без второго резерва баланса. Ответ create/get/list — единый профиль из 19 полей (id,withdrawId,userId,status,requestId,blockchain,currency,amount,wallet,comment,isBalanceCommission,exchangeRate,sentAmount,commissionAmount,transactionHash,callbackUrl,createdAt,lastModifiedAt,sentAt). - Статистика (
POST /v1/payments/incoming/statistics,POST /v1/payments/outgoing/statistics): ответ —{"statistics": [...]}с массивом записей по каждому методу (14 полей для incoming, 12 для outgoing), а не{successCount, totalAmount}. Тело запроса — необязательныеstartDate/endDate(RFC3339). - Диспуты: create/get — полный профиль из 17 полей (
receiptType,receiptUrl,description,clientComment,merchantComment,adminComment,finalizedAt,lastModifiedAtи другие); list — сокращённый из 11. Create читаетpaymentId(обязателен),receiptType,receiptUrl,description,clientComment,merchantCommentи возвращает их эхом. - Банки (
POST /v1/banks/list): элемент списка —BankProfile:id,country,bankName,methodNames,currencies(вместоdisplayName/supportedMethods). - Вебхук PayOut (критично):
PROCESSING→ код40,FAILED/EXPIRED→ код30— ранее оба состояния приходили кодом1(WaitingProcessing), и мерчант не узнавал об ошибке выплаты. Теперь отказ доставляется вебхуком:40обрабатывайте как промежуточный,30— как терминальный отказ. - GET
/v1/payments/incoming/{id}: сделка вREFUND_PENDINGвозвращает статус32(RollingBack); вебхук на той же стадии шлёт16(Returned). Расхождение контуров осознанное и закреплено тестом. Уточнение к записи от 2026-09-08: «32 фактически не выдаётся» устарело — код выдаётся GET-контуром. - Изоляция: GET-ручки (
/payments/incoming|outgoing/{id},/merchant/withdraw/{id},/disputs/{id}) на сущность чужого мерчанта отвечают404 NOT_FOUND— факт существования не раскрывается. - Валюта: неизвестный числовой код в create PayIn/PayOut отвечает
400 INVALID_ARGUMENT(unknown currency code: N); тихая подстановка RUB отключена. - List-фильтры: ручки
withdraw/list,payments/incoming/list,payments/outgoing/list,disputs/listпринимают body-фильтр:status(числовой V1-код),skip,take,startDate,endDate;incoming/listдополнительно —id(UUID).
Инструкция по миграции: интеграциям, написанным по легаси-спеке,
действий не требуется — адаптер вернулся к исходному контракту. Проверьте
парсинг полей статистики/диспутов/банков по спискам выше и заведите ветки
обработки для вебхука PayOut с кодами 40 и 30.
[DOC] Контракты адаптера в Migration guide, вебхук-контур в реестре кодов
- Migration guide дополнен разделом «Контракты
V1-адаптера»: поля withdraw (
wallet+ алиасaddress), идемпотентность поrequestId, полные формы ответов (вывод, статистика, диспуты, банки), list-фильтры, правила изоляции и валюты. - Реестр кодов V1 явно покрывает вебхук-контур: исходящие
40/30в вебхуке PayOut; входящий32(GET) против16(вебхук) на стадии REFUND_PENDING.
2026-09-08
[DOC] Полный реестр числовых кодов V1 (закрывает вопрос интегратора по статусу 50)
Интегратор V1-контура получил от платформы статус 50 (PayingRightNow) по
выплате и не нашёл его в своей копии документации. Проверили: сводного реестра
числовых кодов на сайте не было — коды жили только справочной колонкой в
таблицах V2-статусов, часть значений не показывалась числами вовсе.
- Страница Statuses дополнена секцией «Полный реестр числовых кодов V1»: исходящие (1/40/50 → 10/20/30), входящие (1/31/2/32/21 → 11/12/13/14/15/16, 1000), крипто-выводы (1/2/3/4) — с пометками финальности и перечнем внутренних V2-статусов, которые в каждый код транслируются.
- Колонки «V1 compat» в таблицах PayIn/PayOut заполнены полностью (прочерки
—заменены фактическими кодами: MATCHED/UNASSIGNED →1, EXPIRED-выплата →30, ESCROW_LOCKED →1, PAYMENT_VERIFIED →11, SOFT_DISPUTED →21). - Справка по 50: PayingRightNow — трейдер осуществляет перевод,
промежуточный; исход —
10(успех) или30(отказ);sentAt/sentAmountзаполняются в финальном статусе.
Инструкция по миграции: не требуется. V1-интеграторам — завести у себя весь реестр, чтобы операции не зависали на незнакомых кодах.
[DOC] Уточнения по кодам 20 / 16 / 32 (ответы на вопросы интегратора)
- 20 PaidPartially (исходящие) — легаси-код: текущим движком НЕ выдаётся.
В V2 у выплат нет частичных статусов; финалы — только
10или30. Колонка FAILED в V2-таблице исправлена (было «30/20», стало «30»). - 16 Returned (входящие) — отдаётся вебхук-контуром на стадии REFUND_PENDING (инициация возврата). Не безусловный финал: после возврата сделка может вернуться в рабочий цикл.
- 32 RollingBack — в текущем движке доменный статус не эмитируется, фактически не выдаётся.
- Подтверждение: по исходящим коды
2/13/14/16/21не приходят ни в каком случае (их нет в payout-маппере).
2026-09-08
[FIX] Контракт вебхуков и терминальные исходы — закрытие живого прогона интеграции
Три изменения по итогам полного партнёрского прогона на стейдже:
- Все int64 в телах вебхуков теперь передаются строками — то же
правило, что и в REST-ответах (proto3 JSON):
"amount": "330000","amount_version": "2". Ранееamountшёл голым JSON-числом — парсер, написанный по образцу REST, мог молча промахиваться. Обратите внимание при десериализации: если ваш приёмник ждал число — обновите тип на строку (или гибкий parse). V1-конверт не затронут (легаси-формат). - Повторный
notify-paymentна сделке вPAYMENT_NOTIFIEDотвечает400 FAILED_PRECONDITION(ранее500): состояние сделки корректно, ретраить не нужно (см. Retry-матрицу). - Резолв апелляции
REJECTEDдоводит сделку до терминала: возврат эскроу (order-level и все per-match холды) и переход вCANCELLEDс финальным вебхукомdeal.cancelled(ранее сделка оставалась в промежуточном возврате неограниченно долго).SATISFIED, как и раньше, завершает живую сделку вCOMPLETED.
Инструкция по миграции: для вебхук-приёмников — проверить парсинг сумм (строки); ретраи notify завершать по 4xx; терминальные статусы сделок после резолва обрабатывать как раньше (события аддитивны).
2026-09-04
[FEATURE] Баланс: прозрачность «средств в пути» (pending_credits)
Каждый кошелёк в GET /api/v1/p2p/merchant/balance теперь всегда несёт поле
pending_credits — сумму нетто-зачислений завершённых сделок, ожидающих
фонового распределения комиссий. Раньше между COMPLETED сделки и появлением
нетто в available существовало «окно невидимости»: деньги уже заработаны, но
ни в одной цифре баланса их не было. Обе цифры — доступная и ожидающая —
отдаются всегда, включая нули. Поле additive; кабинет отображает его
как «В пути». Подробности и жизненный цикл — Балансы → Средства в
пути.
Инструкция по миграции: действий не требуется — новое поле просто появилось в ответе; обрабатывайте по «игнорируй неизвестное», если не нужно.
[FIX] V1-конверт: инфраструктурные сбои больше не маскируются под 401/404/400
Внутренний рефакторинг p2p-engine (доступ к данным за одной дверью) закрыл класс мисклассификации в Legacy V1-конверте: при сбое инфраструктуры (недоступность БД и т.п.) endpoint'ы аутентификации и чтения раньше отвечали клиентским кодом ошибки — как будто мерчант не найден или запрос неверен. Теперь границы строгие:
401 UNAUTHORIZED "merchant not found"— только когда мерчант действительно не существует/не аутентифицирован. Инфра-сбой →500INTERNAL_ERROR(лог + метрика на нашей стороне).404 NOT_FOUND "merchant not found"/"withdrawal not found"— только реальное отсутствие ресурса. Инфра-сбой →500 INTERNAL_ERROR.400 INVALID_ARGUMENT(отсутствующий метод оплаты / незарегистрированный slug) — только ошибки запроса. Инфра-сбой →500 INTERNAL_ERROR.
Инструкция по миграции: соответствует уже задокументированной
Retry-матрице:
4xx — не ретраить, 500 INTERNAL_ERROR — ретраить с backoff. Если ваша
V1-интеграция трактует 401/404 как терминальные — поведение корректно и
раньше; изменилось только то, что транзитные сбои теперь видны как 500
и подлежат ретраю, а не выглядят как «мерчант исчез». Форматы тел ошибок и
все остальные коды не менялись. V2 API не затронут. Вебхуки (типы событий,
payload'ы, доставка) не менялись.
2026-09-03
[FEATURE] Изменение суммы сделки (amend) + зачёт просроченного платежа (settle-as-received)
Закрыт фундаментальный раскол кошельков: изменение суммы в кабинете
(POST /deals/{id}/amount) раньше меняло только цифры в БД — эскроу-холды
оставались на старую сумму, комплит подтверждал чужой остаток, книги
расходились. Теперь:
- Amend (живая сделка): разрешён ТОЛЬКО до
PAYMENT_VERIFIED(статусыINITIALIZED/ESCROW_LOCKED/PAYMENT_NOTIFIED/SOFT_DISPUTED— сумма меняема только до подтверждения платежа). Атомарно в одной транзакции: новые цифры (USDT — по курсу самой сделки), пересоздание ВСЕХ холдов (order-level + per-match, ключи от(deal_id, amount_version)), append-only аудит-строкаp2p_deal_amount_changes(причина, оператор, ключ), событиеp2p.deal.amount_changedв outbox. - Оптимистичная блокировка: поле сделки
amount_version(стартует с 1); запрос amend принимаетexpected_amount_version(механизм вида If-Match): два оператора, редактирующие одновременно — второй получает409и перечитывает. Идемпотентность оператора:idempotency_key(UNIQUE в аудите) — повтор = replay, другое тело =409. - Новое событие вебхука
deal.amount_changed(V2-only!):old_amount, новаяamount,amount_version. Legacy V1-конверт не имеетcallbackTypeпод него — V1-мерчантам событие не доставляется (контрактно; как и события каталога). Прочие события не менялись. - Settle-as-received (EXPIRED + received_amount>0): истёкшая сделка
никогда не оживает; по резолву апелляции оператор создаёт НОВУЮ
сделку-сеттлмент
POST /api/v1/p2p/appeals/{appeal_id}/settlement— сумма =received_amountоригинала (или обоснованный override), курс — новый на момент резолва, полный стандартный путь (эскроу → verify → complete), связиparent_deal_id/origin_appeal_id(UNIQUE: одна апелляция → максимум один сеттлмент, повтор — replay). Оригинал остаётсяEXPIREDнавсегда. По сеттлменту приходит обычный набор вебхуковdeal.created…deal.completed(обе версии API). - Кабинет: модалка amend показывает пересчитанный USDT до подтверждения, причина обязательна; в карточке EXPIRED-сделки с полученным платежом — панель «зачесть по факту» (сумма из данных; ручное переопределение — второй шаг подтверждения + причина); листинги помечают сеттлменты и их родителей.
Дока: PayIn → amend и
settle-as-received,
Disputes → сеттлмент,
Callbacks → deal.amount_changed,
Статусы → поля суммы.
Инструкция по миграции: действий мерчанта не требуется. Новые поля
(amount_version, parent_deal_id, origin_appeal_id) — additive; событие
deal.amount_changed обрабатывайте по best-practice «игнорируй неизвестное»,
если изменение суммы вам не значимо. V1-мерчанты новых событий не получают.
Миграции БД: 000017, 000018 (bridge-инкременты, идут с релизом
p2p-engine; для контуров с уже применённой 000005-сквошей — идемпотентны).
2026-09-01
[FIX] Возврат к единому конверту списков: data (откат регресса документации от 08-29)
Регресс документации: правка 2026-08-29 переставила примеры листингов
на внутренние прото-имена полей ({"deals": [...]}, {"payment_methods": [...]}, total_count) — по ошибке: за источник истины был взят proto, а не
фактический HTTP-контракт. Фактическое поведение платформы не менялось с
2026-08-24 (см. запись 2026-08-24: канонический конверт data):
egress-маршалер шлюза отдаёт ВСЕ листинги merchant-контура как
{"data": [...], "next_page_token": ""} и не выносит на HTTP ничего сверх
этого (total_count существует только во внутреннем gRPC-контракте).
- Исправлены все страницы и примеры (index, deals, callbacks, banks, wallet,
disputes, turnover) и
openapi.yaml(схемы и примеры листингов). - Поведение проверено реальными запросами по каждому листингу:
/deals,/callbacks,/appeals,/passes,/banks,/payment-methods,/currencies,/wallet/deposits,/wallet/withdrawals,/deals/turnover— все отвечают{"data": [...], "next_page_token": ...}. - Правило простое: единичный ресурс — напрямую (
{"deal": {...}}); списки — всегдаdata;GET /balance— исключение (не листинг):{"wallets": [...]}. - Сопутствующая находка (ответ интеграторам): в живых каталогах тенантов
мобильного метода (
mobcom_rub) сейчас нет — ни на стейдже, ни на проде; актуальный набор методов мерчанта возвращаетGET /payment-methods.
Инструкция по миграции: не требуется — платформа всегда отвечала
data-конвертом; правилась только документация. Если ваш код уже читает
data — всё верно. Если ориентировались на вчерашние (29.08) примеры с
deals/total_count — замените чтение поля на data и уберите зависимость
от total_count.
[DOC] Единый домен платёжной страницы — checkout.syncra.money
- Канонический вид
payment_url:https://checkout.syncra.money/pay/{hash}(это же — компилируемый дефолт платформы в резолвереDB checkout_base_url → env CHECKOUT_BASE_URL → default). - Из документации вычищен второй домен (
pay.syncra.money/{hash}из Hosted Checkout/Quickstart иopenapi.yaml) — оба источника теперь указывают один и тот же домен.
Инструкция по миграции: не требуется — значение payment_url берите из
ответа создания сделки как есть, не конструируйте домен самостоятельно.
2026-08-29
[FIX] Сверка контрактов №2: код догоняет документацию (вебхуки, rate-limit-заголовки, идемпотентность per-merchant)
Сверка «документация ↔ proto ↔ код» второй волны. Здесь, в отличие от 2026-08-28, правился и код платформы, потому что документация обещала поведение, которого в коде не было. Обратная совместимость сохранена: ничего не удалялось и не сужалось — только аддитивные события, заголовки и защита изоляции.
[FEATURE] [callbacks] — Недостающие webhook-события реализованы в коде
deal.createdдля PayOut — теперь эмитится при создании выплаты (ранее — только у PayIn; status несётMATCHED/UNASSIGNEDна момент ответа создания). Повтор идемпотентного создания не шлёт второй вебхук.deal.expiredдля PayOut — эмитится при EXPIRED-переходе выплаты (в т.ч. по SLA изUNASSIGNED); холд возвращён.p2p.finance.withdrawal_failed(status: CANCELLED) — эмитится при отмене вывода оператором/до broadcast. Ранее событие было задокументировано, но не эмитилось.- Обработчики вебхуков обязаны игнорировать неизвестные типы событий
(best practice добавлена в Callbacks) — с выхода этих
событий мерчанты начнут получать новые
event-типы.
[FIX] [ratelimit] — Заголовки X-RateLimit-* действительно выставляются
- Дока обещала
X-RateLimit-Limit/X-RateLimit-Remaining/X-RateLimit-Reset(Unix-секунды сброса UTC-минуты) на каждом ответе HMAC-контура — код их не ставил. Теперь ставит: и на успешных ответах, и на429. Это аддитивное изменение — клиенты, не читающие заголовки, не затронуты.
[SECURITY] [idempotency] — Ключ идемпотентности скоупится по мерчанту
- PayOut: детерминистичный ключ выплаты теперь выводится из
(tenant, merchant, idempotency_key). Ранее формула не включала мерчанта, а replay-поиск был глобальным — два мерчанта с одинаковой строкой ключа могли сойтись на одной выплате, и второй получал чужую сделку в ответе (утечкаtarget_requisite). Теперь чужая строка под ключом →409(idempotency_key is already used by another merchant), replay — только своей сделки (легаси-нескоупленные ключи по-прежнему резолвятся для незавершённых ретраев). - PayIn: replay через idem-кэш и recovery-путь проверяют владельца найденной сделки (внутри тенанта); чужая сделка больше не возвращается.
- Обещание Идемпотентности («одинаковые ключи у разных мерчантов не конфликтуют») теперь — фактическое поведение платформы.
[DOC] [callbacks] — Точная математика ретраев доставки
- При
max_retries=5(умолчание) доставка пробуется 5 раз суммарно (исходная + 4 повтора, паузы 5s/30s/5m/15m); ступень 1h достижима при повышенном per-merchantmax_retries. Прежний текст «до 5 повторов после исходной» и утверждение про «паузу ≤ 5 минут» были неверны — удалены. - Успех доставки = любой ответ < 400 (включая 3xx), а не только 2xx.
response_bodyв истории доставок обрезается до 2048 байт.
[DOC] [deals] — Пагинация и фильтры листинга по факту
page_size: умолчание 50 (не 20), максимум 250.created_before— включительно (inclusive), как иcreated_after.total_countсчитает фильтрыdeal_type/statuses, но не дата-фильтры — исторический контракт, теперь задокументирован явно.- Листинг апелляций (
GET /appeals) — пагинирован (AIP-158, умолчание 20, максимум 100); прежняя пометка «параметры не применяются» устарела.
[DOC] [migration] — Реальные маршруты V1 compat-слоя
- Таблица сигнатур V1 указывала несуществующие маршруты
(
/v1/merchant/payin|payout|dispute). Исправлено на фактические:POST /v1/payments/incoming,POST /v1/payments/outgoing,POST /v1/merchant/withdraw,POST /v1/disputs/create,POST /v1/banks/list.
[DOC] [misc] — Прочие сверки
GET /wallet/deposit-address: в ответе естьupdated_at; 404-пример приведён к фактическому problem+json (detail — вложенный gRPC-JSON).POST /api-keysпомечен admin-only (ротация — dual-homed).GET /my-merchant— dual-homed (JWT или HMAC); PATCH — только JWT.- Replay-окно подписи: 5 минут назад / 60 секунд вперёд (не «±300 с»); у V1 replay-защита есть (300 с / +60 с), а не «нет».
reasonапелляции: обязательный минимум 10 символов (не «рекомендуется»); текст ошибки актуализирован.- CSV-экспорт:
counterparty=client_idдля PAYIN (пусто, если не передан),target_requisiteдля PAYOUT. - SDK-примеры §1: добавлен обязательный
payment_method_idво все 4 языка (пример без него получал 400); quickstart — «минимально 5 полей». - openapi.yaml: суммы int64 в примерах — строками;
wallet_id— UUID (TRC-адрес убран из примера баланса); kill-switch — bodyless toggle; статусы листинга принимают оба набора (OrderStatus | PayOutStatus); добавлены вебхукиp2p.finance.withdrawal_failedиpayment_methods.updated(каталог-конверт), статусwithdrawal_completed-примера —APPROVED;EmptyResponse— пустой объект; пер-endpointpage_size(deals 50/250, компакт-листинги 20/100).
Инструкция по миграции: не требуется. Новые события вебхуков — additive:
добавьте в фильтр deal.created/deal.expired (PayOut) и
p2p.finance.withdrawal_failed, если хотите на них реагировать; неизвестные
event просто логируйте. Единственное наблюдаемое изменение ошибок —
409 при коллизии idempotency_key между мерчантами (ранее такой запрос
мог вернуть чужую сделку — это был баг).
2026-08-28
[DOC] Сверка контрактов: документация ↔ proto ↔ код
Полная сверка публичной документации с фактическим поведением платформы
(proto platform.p2p.v1 + p2p-engine). Изменения API-контракта — нет:
все правки приводят документацию, openapi.yaml и proto-комментарии к уже
работающему коду. Ничего удаляться не должно — обратная совместимость
сохранена полностью.
[DOC] [payout] — Унифицированная ссылка на метод: UUID и слаг на обеих ногах
POST /deals/payoutpayment_methodпринимает слаг ИЛИ UUID (ранее дока описывала только слаг) — оба значения резолвятся в один и тот же метод, как и в PayInpayment_method_id.- Задокументирован currency cross-check: метод чужой валюты
(
card_rubприcurrency: "TRY") →400 INVALID_ARGUMENT«payment method currency does not match the deal currency». - Неизвестная ссылка →
400со списком допустимых слагов (живой пример добавлен в Коды ошибок).
[DOC] [wallet] — Правда о выводе USDT: статусы, комиссии, идемпотентность
- Синхронный ответ
POST /wallet/withdraw: статус —COMPLETED(авто-проведение) илиPENDING_MANUAL(очередь ручного одобрения), а неAPPROVED/PENDING, как писала дока. Доменные статусы заявки (PENDING/APPROVED/CANCELLED) — отдельная поверхность (листинги и вебхуки); прежний WARNING «COMPLETEDне используется» был неверен и удалён. - Добавлены поля ответа
fee_usdt_cents/net_usdt_cents(USDT-центы). idempotency_keyвывода: поле контракта, но не требуется и не валидируется — идемпотентность гарантируется платформой (детерминированный серверный ключ по мерчант+адрес+сумма+комиссия).- Задокументированы WAVE-эндпоинты кошелька, отсутствовавшие на странице:
GET /wallet/withdraw-fee(квота комиссии до заявки),GET /wallet/deposits(TRC-20 депозиты с custodial-статусами),GET /wallet/withdrawals(история выводов: очередь ручных одобрений, смёрженная с ledger-списаниями).
[DOC] [payin] — amount_match_status: ровно три значения
- Значение
underpaidне существует: канонический набор —full/partial(= поступило меньше) /overpaid, строго lowercase. Исправлено в Deals и proto-комментарииMerchantDealInfo(вместе с регенерациейpb.go).
[DOC] [passes] — Required-поля создания pass'а
- Обязательное поле одно —
amount.currency_id,requisite_type,payment_method_id,provider_id— опциональные фильтры (пусто = «любой»).payment_method_idна pass-ветке принимает только UUID (слаг не резолвится — в отличие от ног сделки). Прежняя версия страницы требовалаcurrency_id+requisite_type— неверна. 409 Conflictзакреплён как единственный в API: конкурентный резерв pass'а (gRPCABORTED, «pass was concurrently reserved»); errors.mdx раньше категорично заявлял «409 не используется».
[DOC] [general] — Kill-switch, export-путь, V1-маппинг, мелкие фиксы
- Kill-switch (
POST /p2p/merchants/{id}/kill-switch): это toggle без тела (никакихactive/reason) и admin-only (не self-service мерчанта; кода ошибкиMERCHANT_DISABLEDне существует — фактически403 merchant is disabled). То же про/disable//enable. - CSV-экспорт: канонический путь теперь
/api/v1/p2p/merchant/deals/export; корневое зеркало/api/v1/p2p/deals/exportпродолжает работать и помечено deprecated (обратная совместимость сохранена). - V1-маппинг PayIn-кода
12— этоCANCELLED(иSPAM_REJECTEDтоже транслируется в12); migration-guide исправлен (12 → SPAM_REJECTEDбыло неверно). - migration-guide: конверт ответов V2 — proto-поля верхнего уровня (никакой
обёртки
{data});payment_method_id— REQUIRED (не optional). - Слаги каталога унифицированы по всей документации:
card_rub/sbp_rub/mobcom_rub(устаревшиеcard/sbp/card2cardубраны из примеров). - balance.mdx: фантомные «типы балансов» MAIN/INSURANCE/DEPOSIT убраны —
GET /balanceвозвращает только фактические кошельки;wallet_id— UUID кошелька, а не TRC-20 адрес. - api-reference: идемпотентность — поле
idempotency_keyв теле (заголовокIdempotency-Keyв Merchant API не используется). - openapi.yaml: добавлены отсутствовавшие пути (
/currencies,/deals/export,/deals/turnover,/callbacks/{id}/retry,/wallet/withdraw-fee,/wallet/deposits,/wallet/withdrawals,/my-merchant), исправлены схемы (withdraw в целых USDT, required-набор CreatePayInRequest, int64-строки в примерах),402заменён на400 FAILED_PRECONDITION.
Инструкция по миграции: не требуется — контракты не менялись, менялась
только документация. Если вы полагались на строку APPROVED в синхронном
ответе вывода — проверьте обработчик: фактическое поведение платформы
(COMPLETED/PENDING_MANUAL) не менялось с момента релиза WAVE 3.
2026-08-26
[FEATURE] PayIn: статус CANCELLED — явная отмена сделки клиентом или мерчем
- Новый терминальный статус
CANCELLEDдля депозитных (PayIn) сделок: отличается отEXPIRED(таймаут) — теперь «клиент активно отменил» и «время вышло» различаются. - Разрешён переход из
INITIALIZED,ESCROW_LOCKED,PAYMENT_NOTIFIED(до подтверждения платежа). Эскроу-холды снимаются автоматически, комиссия обнуляется, матчи →FAILED. - Новые эндпоинты:
POST /api/v1/p2p/merchant/deals/{deal_id}/cancel— отмена мерчем (HMAC), тело{"reason": "..."}опционально; повтор на уже-CANCELLEDсделке → 200 идемпотентно; не-PayIn или недопустимый статус → 400.POST /api/v1/p2p/checkout/{hash}/cancel— отмена клиентом на hosted-checkout (по deal hash); терминальная сделка → 409.
- Новое webhook-событие
deal.cancelled(payload идентиченdeal.expired,status: "CANCELLED"). Мерчи, фильтрующие поdeal.completed, не затронуты. - V1-адаптер:
CANCELLED→ код12(Cancelled), фильтр листингаGET /deals?statuses=принимаетCANCELLED.
Инструкция по миграции: ничего ломать не нужно — изменение additive.
Рекомендуем добавить CANCELLED в списки неуспешных статусов и обработчик
deal.cancelled, если хотите отличать отмены от таймаутов.
2026-08-24
[FIX] PayIn: payment_method_id обязателен
- Ранее: без
payment_method_id→ 500 "payment method is required for order matching" + создавалась нежизнеспособная сделка со статусом EXPIRED. - Теперь: 400 INVALID_ARGUMENT с подсказкой
GET /payment-methods. Заявка не создаётся вообще. payment_method_id— REQUIRED поле. Получите UUID из каталога методов перед созданием PayIn.
Формат записей
Каждая запись в changelog имеет стандартизированную структуру:
- Заголовок: Дата релиза (в формате YYYY-MM-DD) и краткое резюме.
- Тип изменения:
[FEATURE]— Добавление нового функционала или эндпоинта.[FIX]— Исправление ошибок в логике работы API.[DEPRECATION]— Предупреждение об устаревании функционала и планируемом отключении.[BREAKING]— Обратно-несовместимые изменения, требующие обновления вашего кода.
- Категория: Сфера влияния изменения (
payin,payout,auth,callbacks,disputes,balance,general). - Описание: Детальное объяснение изменений и инструкции по обновлению (Migration Path).
Актуальная история версий
2026-08-23 — Волны 7/8: кабинетные эндпоинты и сверка доки с живым стейджем
Все примеры в документации приведены к фактическим ответам стейджа
(api-stage.syncra.money, живые curl). Новые страницы: CSV-экспорт,
Обороты, Профиль и лимиты.
[FEATURE] [general] — CSV-экспорт сделок
GET /api/v1/p2p/deals/export (путь вне merchant-префикса): UNION-выгрузка
PayIn+PayOut, фильтры листинга, Accept: text/csv → сырой RFC 4180-документ
(Content-Disposition: attachment; filename="deals-YYYYMMDD.csv"), без Accept —
JSON-конверт csv/file_name/row_count/truncated. Cap 10 000 строк.
[FEATURE] [general] — Обороты сделок
GET /api/v1/p2p/merchant/deals/turnover?date_from&date_to (RFC3339,
обязательные, окно ≤ 92 дней, deal_type опционален) → rows[]
(date,deal_type,amount_minor,currency); исключены
EXPIRED/SPAM_REJECTED/FAILED.
[FEATURE] [callbacks] — История доставок и ручной retry
GET /callbacks (keyset-пагинация page_token/next_page_token) —
статусы PENDING/SUCCESS/RETRY/ERROR, response_code, retry_count,
next_retry_at. POST /callbacks/{id}/retry: только ERROR/RETRY
(иначе 400), cooldown 60 с (иначе 429), чужой — 403, неизвестный — 404.
[FEATURE] [general] — Лимиты мерчанта и kill_switch_reason
GET/PATCH /api/v1/p2p/my-merchant (кабинетный JWT):
deal_min_amount/deal_max_amount/daily_deal_amount_limit (int64 копейки;
отсутствует = не менять, 0 = снять, >0 = установить); нарушения при создании
сделок: min/max → 400 INVALID_ARGUMENT, daily → 400 FAILED_PRECONDITION.
GET my-merchant отдаёт kill_switch + kill_switch_reason (баннер кабинета).
[FIX] [general] — Сериализация int64 и формат ошибок
Зафиксировано в документации: значения int64 в JSON-ответах передаются
строками ("amount": "100000"); ошибки — RFC 9457 problem+json c
type: "about:blank", вложенными gRPC-деталями в detail и instance
upstream-сервиса; правило «неверное состояние → 400» (не 409).
2026-08-22 — Неделя интеграции: приведение контрактов к фактическому поведению платформы
Волна фиксов 2026-08-21/22 (проверена живым E2E на стейдже). Все изменения ниже отражены в соответствующих разделах документации.
[BREAKING] [general]
Убрана обещанная обёртка data в успешных ответах. grpc-gateway
отдаёт proto-поля верхнего уровня (создание и получение PayIn/PayOut,
листинг, баланс, пропуска, апелляции):
{"deal": {"deal_id": "..."}}
{"deals": [], "next_page_token": "", "total_count": 0}
{"wallets": []}Схемы и примеры openapi.yaml исправлены соответственно.
Инструкция по миграции:
- const { payment_url } = response.data
+ const { payment_url } = response.deal[BREAKING] [general]
Формат всех ошибок — RFC 9457 problem+json. Edge-gateway переписывает
все non-2xx ответы в application/problem+json
({"type","title","status","detail","instance"}); старый конверт
{"error":{"code","message","details"}} больше не возвращается. gRPC-/
doc-коды (INVALID_ARGUMENT, UNAUTHENTICATED, ...) остаются
семантикой (определяют HTTP-статус и текст detail), но полем code в
теле не передаются. См. Коды ошибок.
[FIX] [general]
Новые HTTP-коды вместо «зонтичного» 500:
404 Not Found— сделка не найдена вGET /deals/{deal_id}(ранее500).503 Service Unavailable— «no matching trader available, retry later» при исчерпании каскада (ранее500); retry с backoff.
[FIX] [auth]
Auth hardening недели интеграции (кратко):
- Rotate grace period теперь реален: 15-минутное dual-key окно после
ротации; валидна только целиком старая пара (старый токен + старый
секрет), смешанные комбинации —
401; исходящие вебхуки подписываются новым секретом немедленно; окно настраивается черезMERCHANT_KEY_GRACE_PERIOD(0= off). Поля ответа issue/rotate —token/secret(НЕapi_token/webhook_secret). См. Управление API-ключами. merchant_idв запросе — advisory при HMAC (claims-win): значение (в т.ч. невалидного формата или чужое) молча игнорируется, реальный скоуп — из подписанного токена; запрос возвращает200. Исключение — cabinet-JWT: там несоответствиеmerchant_id→403.
[FIX] [callbacks]
- Per-deal
callback_urlреально override'ит доставку (PayIn): иерархия per-dealcallback_url→merchant.webhook_url; оба пусты — колбэк не создаётся. Для PayOut per-deal override не поддерживается (поля в запросе нет) — толькоmerchant.webhook_url. - Подпись
X-Signatureвебхука считается от байтов тела ровно в том виде, в котором они доставляются (фикс 2026-08-22): подпись и тело гарантированно разделяют один байтовый источник, HMAC мерчанта по полученному телу совпадает. См. Callbacks → Подпись запроса.
[FIX] [payout]
Статусная модель после создания приведена к фактической: синхронный
авто-матчинг каскада возвращает MATCHED (есть свободный трейдер),
иначе UNASSIGNED — retry-воркер продолжает мэтчинг до SLA, по
истечении которого выплата переходит в терминальный EXPIRED (холд
возвращён). deal_type=PAYOUT-сделки видны в GET /deals и
GET /deals/{deal_id}. wallet_id в запросе — USDT-кошелёк списания
(холд в USDT по SELL-курсу), а не fiat-кошелёк; currency в ответе — эхо
ISO-кода fiat-валюты выплаты. См. PayOut.
[FIX] [balance]
GET /balance возвращает реальные кошельки мерчанта из money-сервиса —
одна строка на фактический кошелёк со своей валютой и wallet_id
(синтетических строк нет); frozen отражает активные холды. См.
Балансы.
[FIX] [payin]
GET /deals задокументирован как объединение PAYIN (p2p_orders) и
PAYOUT (p2p_payouts) в единый листинг: payment_url присутствует только
у PAYIN, target_requisite (эхо, без маскировки) — только у PAYOUT;
фильтр deal_type=PAYIN|PAYOUT. См. Deals.
[FIX] [general]
Идемпотентность: формат idempotency_key фактически не валидируется
(любая непустая строка; рекомендуемый формат — UUID v4); повторный вызов с
тем же ключом возвращает ту же сделку без дубля. Убрано обещание лимита в
128 символов / restrictions на символы. См.
Идемпотентность.
[NEW] [balance]
Deposit address — две операции. TRC-20 адрес для зачисления USDT-обеспечения (collateral) на операционный баланс мерчанта (выплаты, крипто-выводы, комиссии; клиенты в PayIn платят по реквизитам трейдеров, НЕ на этот адрес):
GET /wallet/deposit-address— read-only текущий активный адрес;404 problem+json(«deposit address not provisioned yet — POST /wallet/deposit-address to generate one»), если адрес ещё не сгенерирован.POST /wallet/deposit-address— генерация/ротация: каноничный путь безmerchant_id(скоуп — из токена). Ротация немедленно заменяет адрес в строке мерчанта, при этом все ранее выпущенные адреса остаются в постоянном polling-наборе — USDT на старый адрес не теряются. Legacy-путьPOST /merchants/{merchant_id}/deposit-addressостаётся рабочим для admin-impersonation.
См. Кошелёк → TRC-20 адрес для пополнения.
[BREAKING] [general]
trace_id / traceId удалены из тел problem+json. Тело ошибки — строго RFC
9457 поля (type, title, status, detail, instance); идентификатор
трассировки передаётся заголовком X-Trace-Id на каждом ответе с ошибкой
(генерируется gateway'ем, если upstream не предоставил). Именно
его указывайте в обращениях в поддержку. Для браузерных клиентов заголовок
экспонируется через CORS (Access-Control-Expose-Headers). См. Коды ошибок →
Идентификатор
трассировки.
Инструкция по миграции:
- const traceId = response.body.trace_id
+ const traceId = response.headers.get('X-Trace-Id')[DOCS] [payout]
Lifecycle PayOut выпрямлен в Migration Guide и
Справочнике статусов (приведён к PayOut):
синхронный авто-матч каскада при создании возвращает сразу MATCHED либо
UNASSIGNED (retry-воркер перематчивает; по SLA — EXPIRED с возвратом
холда); INITIALIZED — стартовый статус стейт-машины (граничные случаи:
провайдер-ветка/гонки); далее MATCHED → PROCESSING → COMPLETED |
FAILED. В statuses добавлена FSM-диаграмма PayOut.
2026-08-21 — Документация PayIn: единый контракт методов, мобильная коммерция (SIM), пример deal.expired
[FEATURE] [payin]
Явная фиксация принципа единого контракта: POST /deals/payin принимает один и
тот же набор полей для любого метода оплаты — метод-специфичных параметров в
запросе не существует, выбор метода задаётся только payment_method_id.
Специфика канала (тип реквизита, формат отображения, инструкция для плательщика)
инкапсулирована на стороне платформы и показывается на платёжной странице. См.
Прием платежей → Поддерживаемые методы
оплаты.
- В список типовых методов добавлены методы мобильной коммерции
(
RequisiteType: SIM): реквизит для плательщика — номер телефона оператора связи, название оператора отображается как «банк получателя». Номер телефона самого плательщика (MSISDN) не требуется и не передаётся. - Поле
phone_numberвCheckoutViewдокументировано дляSBPиSIM(ранее — толькоSBP).
[FEATURE] [callbacks]
Добавлен пример payload deal.expired — терминального неуспешного исхода
PayIn (истечение payment_timeout_at без поступления средств): поля
received_amount и amount_match_status в нём отсутствуют, сверка сумм не
выполняется. См. Callbacks → Примеры payload по
событиям.
2026-07-12 — Запуск Merchant API V2 и Адаптера V1
[FEATURE] [general]
Официальный запуск Merchant API V2. Новая архитектура предоставляет повышенную стабильность, единый формат интеграции и улучшенные показатели безопасности.
Основные нововведения:
- HMAC-SHA256 подписи: Внедрена унифицированная схема подписи
X-Signatureот тела запроса для всех эндпоинтов вместо разрозненных формул в V1. - Строковые перечисления статусов: Переход с числовых кодов состояний на
строковые String Enums
UPPER_SNAKE_CASE(например, код1→INITIALIZED, код11→COMPLETED), что повышает читаемость логов и кода. Полная таблица соответствия кодов V1 и статусов V2 — в Справочнике статусов. - Суммы в минимальных единицах (копейках): Суммы больше не передаются в виде
строк с точкой (
"1500.00"). Теперь используется целочисленный форматint64(копейки, центы:150000). Это защищает от ошибок округления чисел с плавающей запятой. - OpenAPI-спецификация: Документация теперь содержит полную OpenAPI-схему
(
openapi.yaml) с возможностью импорта в Postman/Insomnia и навигацией по эндпоинтам в боковой панели.
[FEATURE] [general]
Внедрение V1 Compatibility Adapter (Адаптера совместимости).
- Позволяет существующим клиентам, интегрированным с API V1, переключиться на новую инфраструктуру Syncra V2 без изменения своего исходного кода.
- Адаптер автоматически парсит старые заголовки (
MERCHANT,SIGNATURE), проверяет подписи по алгоритму HMAC-SHA512, конвертирует форматы сумм и отдает ответы в старом конверте{failures, value, isSuccess}.
[DEPRECATION] [general]
Объявление о выводе из эксплуатации API V1.
- Период поддержки (Grace Period): до 12 января 2027 года. Адаптер
работает в штатном режиме без ограничений. Уже в этот период каждый ответ V1
несёт стандартные IETF-заголовки машиночитаемой депрекации:
Deprecation: trueSunset: <дата отключения>(RFC 1123)Link: </api/v2...>; rel="successor-version"— V2-эквивалент пути.
- Полное отключение (Sunset): 12 января 2027 года V1 адаптер выключается
(дата по умолчанию; может быть изменена оператором платформы). Все запросы по
старым путям начинают возвращать
410 Goneс RFC 9457 problem details. - Заголовок
X-Syncra-Deprecatedне используется и не будет — сигнализация о депрекации идёт исключительно черезDeprecation/Sunset/Linkвыше.
2026-07-13 — Архитектурная модернизация: Data-Driven Currencies и Appeals API
[BREAKING] [payout]
Поля card_number и full_name в PayOut переименованы в target_requisite и
target_name для поддержки всех типов реквизитов (карта, СБП, e-wallet). V1
адаптер обрабатывает маппинг автоматически.
Инструкция по миграции:
- "card_number": "2202201234567890"
- "full_name": "Иван Иванович"
+ "target_requisite": "2202201234567890"
+ "target_name": "Иван Иванович"[FEATURE] [disputes]
Добавлен полноценный Appeals API для мерчантов:
POST /appeals— открытие диспута по сделкеGET /appeals— список диспутов с фильтрациейGET /appeals/{id}— получение диспутаPOST /appeals/{id}/messages— отправка сообщения в чатGET /appeals/{id}/messages— история чата
[FEATURE] [general]
Переход на data-driven конфигурацию: валюты, страны, лимиты и методы оплаты теперь управляются через админ-панель без перезапуска сервисов.
[FEATURE] [balance]
Мультивалютные балансы: поле currency теперь отражает реальную валюту тенанта.
USDT wallet_id содержит реальный TRC-20 адрес мерчанта.
Август 2026 — TRY/Havale Channel, Antifraud, White-label и DO Migration
2026-08-18 — Merchant API v0.2.11: канал TRY/Havale для выплат
[FEATURE] [payout]
Выплаты в Турции по IBAN (Havale): в POST /deals/payout добавлены два
optional-поля:
target_bank(string) — банк получателя (слаг или название, напримерziraat); используется для inside-bank каскадного матчинга.requisite_details(map<string,string>) — именованные реквизиты получателя; допустимые ключи:iban,account_number,document_number,bank_name,phone_number. Неизвестные ключи отклоняются с400и списком допустимых.
См. PayOut → Выплаты TRY / IBAN (Havale).
[FEATURE] [general]
Новый справочный эндпоинт GET /api/v1/p2p/merchant/payment-methods —
активные методы тенанта мерчанта (id UUID, slug, name,
requisite_types, currency_code, status). Это канонический источник
значения payment_method_id для POST /deals/payin и слагов
payment_method для POST /deals/payout. GET /banks UUID не
возвращает (его methods — типы реквизитов RequisiteType).
См. Banks → Получение списка методов через API.
Инструкция по миграции: если вы программно получали payment_method_id
из GET /banks — переключитесь на GET /payment-methods; если UUID были
захардкожены — сверьте их с ответом нового эндпоинта после провижининга
методов тенанта.
[FIX] [payin]
Страница PayIn приведена к фактическому поведению API:
- Исправлен источник
payment_method_id: в таблицах параметров ранее ошибочно указывалсяGET /banks; канонический источник UUID —GET /payment-methods(см. выше). Аналогичная правка внесена в Quickstart и Migration Guide. - Статус в примере ответа
200 OK—ESCROW_LOCKED: подбор трейдера и эскроу-лок выполняются синхронно в рамках запроса, при неудачном матчинге вызов завершается ошибкой, а не «висящей» сделкой.INITIALIZED— кратковременный транзитный статус, в ответе создания практически не наблюдается (возможен вGET /dealsдо завершения подбора). - Добавлен пример ответа для TRY (
BANK_TRANSFER/ Havale). - Новая секция «Где реквизиты получателя при PayIn»
(PayIn): реквизиты
получателя (PAN, телефон СБП, IBAN) никогда не возвращаются в теле
ответа
POST /deals/payin(PII-минимизация). Клиент получает их на платёжной странице через публичный эндпоинтGET /api/v1/p2p/checkout/{hash}(контрактCheckoutView:iban,holder_name,payment_method_name,card_number/phone_number); в терминальных состояниях чувствительные поля обнуляются.
[FIX] [payout]
Задокументирована конвенция эха реквизитов выплаты в V2: target_requisite
и значения requisite_details в ответах — эхо данных из запроса мерчанта,
без маскировки (это собственные реквизиты получателя мерчанта, а не данные
трейдера; в логах платформа сохраняет только набор ключей). Маска вида
220220******7890 осталась только в legacy-эндпоинтах V1
(GET /v1/payments/outgoing/{id} и callback-конверте V1). См.
PayOut → Выплаты TRY / IBAN (Havale).
[FIX] [callbacks]
Явное уточнение в Callbacks: платёжные реквизиты в вебхуки
не передаются — никогда. Тела вебхуков (V2 CallbackEventPayload и legacy
V1-конверт) содержат только идентификаторы, суммы, валюту и статус: полные
PAN/IBAN/телефоны мерчанту не отправляются. В V1-конверте cardNumber
приходит только замаскированным (последние 4 цифры), чувствительные
значения в provider_metadata редуцируются. Исчерпывающий источник
реквизитов для PayIn — публичный GET /api/v1/p2p/checkout/{hash}.
[FIX] [general]
Migration Guide — исправления и восстановленные справочники:
- Статусы PayOut: исправлена фактическая ошибка — статус
EXPIREDв V2 существует (таймаут), статусаCANCELLEDнет. Реальный жизненный цикл:INITIALIZED→MATCHED→PROCESSING→COMPLETED; при отсутствии трейдеров —UNASSIGNED, при ошибке —FAILED, по таймауту —EXPIRED(в V1EXPIRED/FAILEDмаппятся в код30). - Таблица маппинга статусов PayIn (V1 код → V2 enum): все коды
1–31, включая переплату/недоплату —14/15→COMPLETED+amount_match_status: overpaid/partial. - Маппинг полей PayIn: V1
method→ V2payment_method_id(UUID изGET /payment-methods); поляpayment_methodдля PayIn в V2 нет. - Восстановлен справочник «V1 сигнатуры запросов» — payload-форматы
Compatibility Adapter по всем 4 legacy-эндпоинтам: PayIn/PayOut —
{timestamp};{id};{salt}, Withdraw — 4 поля (включаяaddress), Dispute — 2 поля без соли; replay-защита:timestampне старше 300 секунд и не более чем на 60 секунд в будущем.
[FIX] [general]
Прочие документационные правки:
- Banks: убрано нереализованное обещание о «временном
исчезновении» банков из справочника — состав
GET /banksформируется из активных методов тенанта (p2p_payment_methods.status = ACTIVE); доступность конкретного трейдера влияет на матчинг сделки, а не на справочник. - Обзор Merchant API и этот changelog: дата полного отключения
(sunset) V1 зафиксирована как 12 января 2027 года (согласно коду
платформы); каждый ответ V1 несёт заголовки
Deprecation,SunsetиLink; rel="successor-version". - Quickstart: статусы в примерах выровнены с реальным
поведением — создание PayIn возвращает
ESCROW_LOCKED(былоPENDING), вебхук —amount_match_status: full(былоMATCHED).
2026-08-09 — DO Migration Prep и выравнивание документации
[FEATURE] [general]
Подготовка инфраструктуры к DO-migration (Digital Ocean переезд):
- RedPanda in-cluster — добавлен для событийной шины (замена managed Kafka).
- Vault HA — High Availability режим для секретов.
- Blue/Green деплой — стратегия бесшовного переключения между окружениями.
- Module-registry переведён на SASL/SCRAM-аутентификацию.
[FIX] [general]
Документационный аудит: все страницы Merchant API выровнены под V2 canonical
contract. Унифицированы X-Signature (не X-Syncra-Signature), payment_url
(не payment_page_url), deal_id (не id), tron_address (не address).
Удалены несуществующие mapping'и из migration-guide.
2026-08-05 — Исправление 4 P0-багов в клиентской аналитике
[FIX] [general]
Исправлены 4 критических бага (P0) в детальной статистике клиентов и antifraud-аналитике:
- Detail stats — некорректный расчёт статистики по сделкам клиента.
- Double-count — задвоение депозитов при определённых переходах статусов.
- Error mapping — неправильная трансляция доменных ошибок в HTTP-коды.
- Fingerprint — обработка пустого/невалидного device fingerprint.
2026-08-04 — IBAN/BANK_TRANSFER и улучшения V1 compat adapter
[FEATURE] [payin]
Добавлен тип реквизита BANK_TRANSFER с поддержкой IBAN для
международных рынков (Turkish Havale, SEPA). Клиент видит IBAN получателя, ФИО
держателя счёта и название банка/канала. См. Hosted Checkout → Перевод по
IBAN.
[FIX] [general]
Улучшения V1 Compatibility Adapter: исправлены withdrawal callback, статус
OVERPAID и URL prefix (3 P0-пробела закрыты).
2026-08-03 — Antifraud stack и White-label brand_config
[FEATURE] [general]
Запуск полноценного Antifraud-стека. См. Antifraud:
- Velocity protection — лимит 10 PayIn/час на
client_id, ошибкаCLIENT_RATE_LIMITED(HTTP 429). - Customer blocking —
IsBlocked+BlockedUntil, ошибкаCUSTOMER_BLOCKED(HTTP 403). - Device fingerprinting — заголовок
X-Player-Fingerprint, SHA-256 хэш, GDPR-compliant. - Auto-segmentation — FTD → STD при 3 депозитах, STD → TRUSTED при 10. VIP — ручной оверрайд.
- Player tracking — сохранение
FirstIP/LastIP/LastUserAgentна записи клиента. - Trust-based cascade routing — правила каскада могут фильтровать команды по
customer_type.
[FEATURE] [payin]
White-label brand_config для payform — мерчанты могут кастомизировать
платёжную страницу (primary_color, logo_url, favicon_url, brand_name,
theme) через админ-панель. См. Hosted Checkout →
Брендирование.
[FIX] [general]
RLS purge — корректная очистка Row-Level Security политик при операциях обслуживания.
Шаблон для добавления будущих записей
При внесении любых изменений в API разработчики Syncra обязаны добавлять новую запись в конец этого документа по следующему шаблону:
### YYYY-MM-DD — Заголовок изменения
#### `[FEATURE|FIX|DEPRECATION|BREAKING]` `[категория]`
Детальное описание изменений для разработчиков мерчантов.
**Инструкция по миграции (если применимо):**
Что именно нужно переписать на стороне мерчанта.