YuppiPayAPI v1
UK RU EN
Merchant Integration Reference

Приём и выплаты через один согласованный контракт

P2P-платёжная система: создавайте заявки на приём и выплаты, сверяйте статусы, получайте вебхуки с единым каноническим форматом. Все суммы — в обычных единицах, 2 знака после запятой.

BASE URL https://yuppipay.net/api/v1

Обзор

Каждый запрос подписывается и идёт на версионированный путь /api/v1/…. Версия живёт в пути: когда появится v2, ваша интеграция на v1 продолжит работать без изменений. Внутри версии мы лишь добавляем поля — никогда не убираем и не переименовываем.

МетодПутьНазначение
POST/ordersСоздать заявку на приём, получить реквизит для плательщика
GET/orders/{uuid}Текущее состояние заявки
POST/payoutsСоздать выплату на карту получателя
GET/payouts/{uuid}Текущее состояние выплаты
POST/payouts/{uuid}/cancelОтменить выплату, которую ещё не взял трейдер
GET/balanceБаланс и оборот в разрезе валют

Аутентификация & подпись

К каждому запросу добавьте два заголовка. Порядок проверки на сервере: лимит частоты → целостность тела → ключ → подпись, поэтому ошибка называет именно свою причину.

ЗаголовокЗначение
X-API-KeyВаш api_key (публичный идентификатор мерчанта)
X-SignatureHMAC-SHA256(<сырое тело запроса>, api_secret) в hex
Content-Typeapplication/json

Подписываются точные байты тела, которые вы отправляете — сериализуйте JSON один раз и подпишите именно эту строку. У GET-запросов тело пустое, поэтому X-Signature — это HMAC пустой строки с api_secret.

Два разных секрета — не перепутайте

api_secret подписывает ваши запросы к нам. webhook_secret нужен, чтобы вы проверяли наши колбеки. Проверка колбека не тем секретом — самая дорогая ошибка интеграции.

Пример подписи (bash)
# тело, которое отправляем
BODY='{"order_id":"A-1001","amount":1500.00,"currency":"UAH"}'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$API_SECRET" | sed 's/^.*= //')

curl -X POST https://yuppipay.net/api/v1/orders \
  -H "Content-Type: application/json" \
  -H "X-API-Key: $API_KEY" \
  -H "X-Signature: $SIG" \
  -d "$BODY"

Дополнительно: если в профиле задан белый список IP, запросы принимаются только с него (проверяется REMOTE_ADDR, X-Forwarded-For игнорируется).

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

Заявки и выплаты идемпотентны по паре (мерчант, order_id). Точный повтор того же запроса возвращает оригинальный объект (не создаёт дубль). Тот же order_id с изменённым телом (сумма, валюта, плательщик / карта получателя) — это конфликт 409 idempotency_conflict, в ответе будет существующий объект.

Практика

Ставьте собственный order_id и безопасно ретрайте сетевые сбои тем же запросом — дубля не будет. callback_url не входит в отпечаток: сменить адрес доставки и повторить — это та же заявка.

Заявки на приём

POST/api/v1/orders

Создаёт заявку payin и возвращает реквизит (карту), который показываете плательщику. Комиссия удерживается из суммы (inclusive): мерчанту зачисляется amount − commission.

Параметры запроса
ПолеТипОписание
order_idstringrequiredВаш внешний номер. Ключ идемпотентности. До 255 символов.
amountnumberrequiredСумма в обычных единицах, ≥ 0.01, 2 знака.
currencystring(3)requiredISO-код валюты. Должна быть активна и включена для приёма.
payer_idstringoptionalИдентификатор плательщика. Участвует в роутинге и идемпотентности.
payer_bankstringoptionalБанк плательщика. Используется только когда включено правило совпадения банка.
callback_urlstring(url)optionalАдрес колбека для этой заявки. Пусто → адрес из кабинета.
Запрос
{
  "order_id": "A-1001",
  "amount": 1500.00,
  "currency": "UAH",
  "payer_id": "user-42"
}
Ответ · 201
{
  "id": "7af7c1df-0629-42d4-9068-df6500b3acd6",
  "order_id": "A-1001",
  "status": "awaiting_payment",
  "amount": 1500.00,
  "amount_settled": null,
  "commission": 30.00,
  "currency": "UAH",
  "exchange_rate": 45.58,
  "settlement_currency": "USDT",
  "payer_id": "user-42",
  "requisite": {
    "card_number": "5375 4141 0000 1234",
    "card_holder": "ПЕТРЕНКО ІВАН",
    "bank": "PrivatBank",
    "type": "card"
  },
  "created_at": "2026-08-09T10:00:00+00:00",
  "expires_at": "2026-08-09T10:15:00+00:00",
  "completed_at": null
}

Показывайте requisite.card_number плательщику; ждите вебхук order.completed (не поллинг). Поле id — это {uuid} для запроса статуса.

GET/api/v1/orders/{uuid}

Возвращает текущее состояние заявки (тот же объект, что и POST /orders). Заявка другого мерчанта невидима даже по известному UUID.

Статусы заявки
awaiting_paymentРеквизит выдан, ждём оплату плательщика
processingОплата видна, идёт сверка зачисления
completedУспешно зачислено — вебхук order.completed
expiredНе оплачено вовремя — вебхук order.expired
cancelledОтменена оператором — вебхук order.cancelled
disputedОткрыт спор; решение закроет заявку — вебхук order.disputed
POST/api/v1/orders/{uuid}/cancel

Снять заявку, которую ещё не оплатили: плательщик передумал или оператор закрывает её с вашей стороны. Карта трейдера сразу освобождается под следующую заявку. В ответ приходит тот же объект заявки со статусом cancelled, а на ваш callback_url идёт событие order.cancelled.

Тело не обязательно. Можно передать reason — причина попадёт в вебхук, в журнал и в карточку заявки, где её видит оператор платёжной системы, поэтому пишите понятный текст, а не служебный код; пустое тело {} тоже принимается, тогда запишется «Cancelled by merchant». Подписывайте ровно ту строку, которую отправляете.

Отменить можно только неоплаченную заявку — статусы pending и awaiting_payment. Как только плательщик перевёл деньги (processing) или открыт спор, отмена через API уже не работает: средства могли поступить на карту трейдера, и такой случай закрывает поддержка. Ответ — 409 order_not_cancellable и текущее состояние заявки в поле order.

Выплаты

POST/api/v1/payouts

Создаёт выплату на карту получателя. Комиссия добавляется сверху (on-top): с мерчанта списывается amount + commission = amount_charged.

Параметры запроса
ПолеТипОписание
order_idstringrequiredВаш внешний номер выплаты. Ключ идемпотентности.
amountnumberrequiredСумма перевода, ≥ 0.01, 2 знака.
currencystring(3)requiredISO-код. Активна и включена для выплат.
recipient_cardstringrequiredНомер карты получателя (до 32).
recipient_namestringoptionalИмя получателя. Участвует в идемпотентности.
recipient_bankstringoptionalБанк получателя.
callback_urlstring(url)optionalАдрес колбека для этой выплаты.
Ответ · 201
{
  "id": "b1e2c3d4-5678-90ab-cdef-1234567890ab",
  "order_id": "P-2001",
  "status": "pending",
  "amount": 1000.00,
  "commission": 15.00,
  "amount_charged": 1015.00,
  "currency": "UAH",
  "exchange_rate": 45.58,
  "settlement_currency": "USDT",
  "recipient": { "card_number": "4149 •••• •••• 3179", "name": "IVAN P.", "bank": "A-Bank" },
  "receipt_attached": false,
  "created_at": "2026-08-09T10:00:00+00:00",
  "expires_at": "2026-08-09T14:00:00+00:00",
  "completed_at": null
}
GET/api/v1/payouts/{uuid}

Состояние выплаты (тот же объект, что и при создании), scoped к вашему мерчанту.

Статусы выплаты
pendingВ общем пуле, ждёт трейдера
assigned / processingВзято в работу — вебхук payout.processing
completedВыполнено — вебхук payout.completed, есть tx_receipt
expiredСрок исчерпан
cancelledОтменено
disputedОткрыт спор
POST/api/v1/payouts/{uuid}/cancel

Отозвать выплату, пока её никто не взял. Тело пустое — отправьте {} и подпишите ровно эти два байта. В ответ приходит тот же объект выплаты со статусом cancelled, а на ваш callback_url уходит событие payout.cancelled.

Как только выплату взял трейдер, отменить её через API уже нельзя: перевод мог начаться. Такой случай закрывает поддержка — ответ 409 payout_not_cancellable и текущее состояние выплаты в поле payout.

Баланс

GET/api/v1/balance

Баланс и оборот в разрезе валют, считаются так же, как в кабинете: зачислено − выдано − холд под незавершённые выплаты − заявки на вывод + правки (заявки — в резервирующих статусах; правки — ручные корректировки администратора, поле adjustments).

Ответ · 200
{
  "merchant_id": 1,
  "balances": [
    {
      "currency": "UAH",
      "balance": 598229.84,
      "settled": 6449109.84,
      "paid_out": 5849865.00,
      "held": 1015.00,
      "withdrawn": 0.00,
      "adjustments": 0.00,
      "turnover": 7010100.00,
      "commission": 560792.16,
      "orders": 438,
      "payouts": 56,
      "withdrawals": 0
    }
  ],
  "generated_at": "2026-08-09T10:15:00+00:00"
}
held - деньги под незавершённые выплаты

Созданная выплата сразу откладывает сумму с комиссией: она уходит в held и уменьшает balance. Выполнение переносит её в paid_out, отмена или протухание возвращает в доступные.

Вебхуки

Мы отправляем POST на ваш адрес доставки на ключевых переходах — завершение и просрочка заявки, коррекция суммы, и изменения выплат — а не буквально на каждое изменение статуса. Отвечайте 2xx быстро; тело обрабатывайте идемпотентно.

Заголовки доставки
Content-Typeapplication/json
X-EventТип события, напр. order.completed — можно роутить обработчики, не разбирая тело
X-SignatureHMAC-SHA256(<сырое тело>, webhook_secret) — сверяйте до обработки
Проверка подписи

Считайте HMAC над сырыми байтами тела (до JSON-парсинга) и секретом webhook_secret — не api_secret. Сравнивайте константным временем.

События заявок

Канонический payload одинаков для всех событий order.*: неизменный порядок и названия полей.

СобытиеКогда отправляется
order.completedЗаявка успешно закрыта — зачисляйте как депозит
order.amount_correctedСумму уже завершённой заявки исправили. Приходит вместо повторного order.completed
order.expiredЗаявку не оплатили вовремя
order.cancelledЗаявку отменил оператор, холд возвращён. Дополнительное поле: reason
order.disputedПо заявке открыт спор, финальный статус ещё не определён. Дополнительное поле: dispute_id

Ручной повтор из админки отправляет тот же payload с дополнительным флагом "resent": true.

Каноническое тело · order.completed
{
  "event": "order.completed",
  "order_id": "7af7c1df-0629-42d4-9068-df6500b3acd6", // наш uuid
  "merchant_order_id": "A-1001",                      // ваш номер
  "status": "completed",
  "currency": "UAH",
  "amount": 1500.00,                                  // заявленная (не меняется)
  "amount_received": 1500.00,                         // фактически полученная
  "amount_settled": 1470.00,                          // к зачислению (минус комиссия)
  "exchange_rate": 45.58,
  "settlement_currency": "USDT"
}
Ключевые суммы

Зачисляйте игроку/клиенту по amount_received (фактически полученная), а amount — это первоначальная заявка, она никогда не меняется. amount_settled — то, что ляжет на ваш баланс после комиссии.

Расширенное тело · order.amount_corrected
{
  "event": "order.amount_corrected",
  "order_id": "7af7c1df-0629-42d4-9068-df6500b3acd6",
  "merchant_order_id": "A-1001",
  "status": "completed",
  "currency": "UAH",
  "amount": 1500.00,
  "amount_received": 1400.00,
  "amount_settled": 1372.00,
  "exchange_rate": 45.58,
  "settlement_currency": "USDT",
  "correction_id": 42,
  "reason": "partial_payment",
  "amount_original": 1500.00, // сумма до коррекции
  "amount_corrected": 1400.00 // новая сумма
}
Зачем отдельное событие

Если сумму исправили после того, как заявка уже была completed (и вы уже получили order.completed), повторный order.completed привёл бы к двойному зачислению. Поэтому приходит order.amount_correctedобновите сумму, не зачисляйте повторно.

События выплат

СобытиеКогда
payout.processingВыплату взяли в работу
payout.completedВыплата выполнена (есть tx_receipt)
payout.expiredСрок выплаты исчерпан
payout.cancelledВыплата отменена
payout.disputedПо выплате открыт спор
Тело вебхука выплаты
{
  "event": "payout.completed",
  "payout_id": "b1e2c3d4-5678-90ab-cdef-1234567890ab", // наш uuid
  "order_id": "P-2001",                                // ВАШ внешний номер
  "amount": 1000.00,
  "amount_charged": 1015.00,                           // списано с мерчанта
  "currency": "UAH",
  "exchange_rate": 45.58,
  "settlement_currency": "USDT",
  "status": "completed",
  "tx_receipt": "https://.../receipt.jpg",
  "completed_at": "2026-08-09T10:15:00+00:00"
}
Курс заявки

exchange_rate - курс settlement_currency к валюте документа, зафиксированный в момент создания заявки или выплаты и дальше неизменный. По нему считайте свой средний курс. У старых документов без курса поле отдаётся как null.

Семантика order_id отличается

В payout-событиях order_id — это ваш внешний номер, а uuid выплаты лежит в payout_id. В order-событиях наоборот: order_id — это наш uuid, а ваш номер — merchant_order_id. Маппьте аккуратно.

Доставка & ретраи

Доставка асинхронная через очередь. Успех — любой 2xx. Не-2xx или таймаут (connect 5с / read 15с) планирует повтор с нарастающей задержкой.

Попытка12345+
Задержка30с60с5 мин15 мин1 ч

До 5 попыток; после исчерпания колбек помечается failed. Обрабатывайте идемпотентно по order_id/payout_id — тот же колбек может прийти повторно.

Коды ошибок

Все ошибки — JSON с полем error (машинный код) и message. Делайте switch по error, не по тексту.

HTTPerrorКогда
400empty_bodyПустое тело в POST
400malformed_jsonТело — не валидный JSON
400content_type_missingНе отправлен Content-Type: application/json
401api_key_missingНет заголовка X-API-Key
401api_key_invalidКлюч не совпадает ни с одним мерчантом
401signature_missingНет заголовка X-Signature
401signature_invalidПодпись не совпала с HMAC тела
403merchant_suspendedМерчант приостановлен/заблокирован
403ip_not_allowedIP не в белом списке
404order_not_foundЗаявка не найдена (или чужая)
404payout_not_foundВыплата не найдена (или чужая)
404unsupported_api_versionПуть без версии или неизвестная версия
404endpoint_not_foundВерсия есть, пути нет (в ответе — список доступных)
405method_not_allowedПуть есть, но не для этого метода
409idempotency_conflictТот же order_id с изменённым телом
422validation_failedПоля не прошли валидацию (детали — в details)
422amount_out_of_rangeСумма вне min/max (есть в теле)
422currency_not_supportedВалюта не активна на площадке
422currency_not_enabledПриём этой валюты не включён для мерчанта
422payout_not_enabledВыплаты этой валюты не включены
422insufficient_balanceБаланс мерчанта вместе с лимитом минуса не покрывает сумму выплаты с комиссией. В теле - required, balance, overdraft_limit
429merchant_limitДневной лимит суммы или количества
429Лимит частоты: 600 запросов в минуту на ключ. Ответ стандартный для сервера и поля error не содержит — проверяйте код 429 до разбора тела. Заголовок Retry-After указывает, сколько ждать
503no_requisite_availableНет свободного реквизита (есть Retry-After: 5)
503rate_unavailableДля валюты не настроен курс - заявка/выплата не создана
503accepting_suspendedKill-switch блокирует приём
503payouts_suspendedKill-switch блокирует выплаты

YuppiPay · Merchant API v1 · все суммы в обычных единицах (2 знака), время в ISO 8601 (UTC). Секреты управляются в кабинете мерчанта → «Интеграция».