PAYNEX — документация по интеграции для мерчантов
P2P-процессинг депозитов и выплат. Вы (мерчант) создаёте заявки через API, клиент переводит деньги другому клиенту напрямую, а PAYNEX сопоставляет заявки, проводит проверки и присваивает операции статус. Итог приходит вам вебхуком.
- Base URL:
https://paynex.work - Личный кабинет:
https://paynex.work/lk— транзакции, журнал вебхуков, ключи, webhook URL, апелляции. - Все суммы — в копейках (5000.00 ₽ =
500000). Никаких float. - Формат — JSON, UTF-8.
Как пользоваться документацией
| Задача | Куда перейти |
|---|---|
| Запустить первый запрос за несколько минут | Быстрый старт |
| Разобраться во всём процессе | эта страница |
| Заполнить поля, выполнить запрос и скопировать динамический код | Swagger |
| Выбрать готовый пример на PHP, Python, JavaScript, Go или cURL | ReDoc |
Merchant API в одной таблице
| Что сделать | Метод | Что считать результатом |
|---|---|---|
| Создать Pay-In | POST /api/v1/payments |
payment_id и checkout_url |
| Проверить Pay-In | GET /api/v1/payments/{payment_id} |
текущий статус депозита |
| Создать Pay-Out | POST /api/v1/payouts |
payment_id выплаты |
| Проверить Pay-Out | GET /api/v1/payouts/{payout_id} |
текущий статус выплаты |
| Проверить расчёты | GET /api/v1/balance |
комиссионный баланс в центах USDT |
| Посмотреть основной webhook | GET /api/v1/webhook |
сохранённый URL или null |
| Установить основной webhook | PUT /api/v1/webhook |
новая текущая настройка |
Pay-In и Pay-Out имеют отдельные методы статуса, поэтому каждое направление легко отслеживать самостоятельно. Финальный результат обработки приходит единым способом — в статусе операции и подписанном webhook.
1. Авторизация
Передавайте ваш ключ одним из двух способов (что удобнее) — оба равнозначны:
X-API-Key: paynex_live_xxxxxxxxxxxxxxxxxxxx
или
Authorization: Bearer paynex_live_xxxxxxxxxxxxxxxxxxxx
В личном кабинете этот ключ называется Bearer Token. Полное значение показывается ровно один раз — при онбординге или ротации; после этого в ЛК → Настройки доступен только безопасный preview/fingerprint. Если ключ утрачен, перевыпустите его: старый ключ по умолчанию принимается ещё 24 часа (grace period), затем автоматически отзывается. Повторная ротация не накапливает ключи: валидны максимум новый current и один grace key.
Отдельно есть Sign Key (он же api_secret) — он не передаётся в запросах, а нужен
только для проверки подписи входящих вебхуков (см. §7). Sign Key также показывается только
при выпуске/перевыпуске. Храните оба значения только на сервере и не отправляйте их в URL,
логи или клиентский JavaScript.
Ошибки авторизации: 401 (нет/неверный ключ), 403 (мерчант отключён).
2. Создать депозит (Pay-In)
POST /api/v1/payments
Заголовки: X-API-Key, Content-Type: application/json, опц. Idempotency-Key.
| Поле | Тип | Обяз. | Описание |
|---|---|---|---|
amount_kopecks |
int | да | сумма в копейках (1..10^9) |
currency |
string | нет | по умолч. "RUB"; сейчас поддерживается только RUB |
payment_method |
string | да | "card" или "sbp" |
merchant_player_id |
string | да | ваш ID клиента (напр. "user_42") |
player_full_name |
string | нет | ФИО клиента из вашего KYC. Рекомендуется: включает анти-треугольник (сверку с ФИО на чеке) |
merchant_payment_id |
string | нет | ваш уникальный ID операции; см. правила идемпотентности ниже |
return_url |
string | нет | абсолютный HTTPS URL возврата на стандартном порту 443 |
webhook_url |
string | нет | Публичный HTTPS URL вебхуков для этого платежа (иначе берётся дефолтный мерчанта) |
external_metadata |
object | нет | любые ваши данные, вернём их в вебхуках без изменений |
expires_in_seconds |
int | нет | TTL заявки, 60..21600, по умолч. 1800 |
Запрос:
curl -X POST https://<домен>/api/v1/payments \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-H "Idempotency-Key: dep-2025-01-15-0001" \
-d '{
"amount_kopecks": 500000,
"currency": "RUB",
"payment_method": "sbp",
"merchant_player_id": "user_42",
"player_full_name": "Иванов Иван Иванович",
"merchant_payment_id": "order_98765"
}'
Ответ 201:
{
"payment_id": "de09a768-28ff-4b91-920e-3d6c27f4cfb6",
"status": "matching",
"direction": "payin",
"amount_kopecks": 500000,
"currency": "RUB",
"payment_method": "sbp",
"checkout_url": "https://<домен>/pay/Qc6CllN0...",
"expires_at": "2025-01-15T08:27:28Z",
"created_at": "2025-01-15T07:57:28Z",
"merchant_payment_id": "order_98765"
}
Дальше: редиректните клиента на checkout_url. На этой странице он видит реквизиты
получателя, переводит деньги и загружает чек. Вам ничего больше вызывать не нужно —
финальный статус придёт вебхуком. Итог сверяйте по payment_id (или вашему merchant_payment_id).
⚠️ Сумма может быть округлена под шаг матчинга мерчанта. Тогда в вебхуке будет
amount_kopecks(фактическая) иoriginal_amount_kopecks(что вы запросили). Списывайте/ зачисляйте по фактической.
3. Создать выплату (Pay-Out)
POST /api/v1/payouts — здесь обязательны реквизиты получателя.
Общие поля: amount_kopecks, currency (сейчас только RUB), payment_method (card/sbp),
merchant_player_id, опц. merchant_payment_id, expires_in_seconds.
Для Pay-Out поле
webhook_urlсейчас не принимается. События выплаты отправляются только на дефолтный URL мерчанта из ЛК → Настройки. Per-paymentwebhook_urlподдерживается только вPOST /api/v1/payments(Pay-In).
payment_method = "card"→recipient_card_number(12–19 цифр), опц.recipient_card_holder.payment_method = "sbp"→recipient_sbp_phone(любой формат), опц.recipient_sbp_bank_code(код из текущего справочника банков в ЛК, напр.SBER,TCS,ALFA,VTB).
Запрос (СБП):
curl -X POST https://<домен>/api/v1/payouts \
-H "X-API-Key: $API_KEY" -H "Content-Type: application/json" \
-H "Idempotency-Key: payout-2025-01-15-0001" \
-d '{
"amount_kopecks": 500000, "currency": "RUB", "payment_method": "sbp",
"merchant_player_id": "user_42",
"merchant_payment_id": "withdrawal_98765",
"recipient_sbp_phone": "+7 999 123-45-67",
"recipient_sbp_bank_code": "SBER"
}'
Ответ 201 содержит payment_id, status, recipient_last4 (для визуальной сверки) и пр.
К выплатам применяется тот же шаг округления мерчанта: amount_kopecks в ответе и
вебхуке — фактическая сумма, а запрошенная до округления приходит в webhook-поле
original_amount_kopecks.
4. Жизненный цикл и статусы
Статус Pay-In запрашивается в разделе платежей:
GET /api/v1/payments/{payment_id}
Статус Pay-Out имеет отдельный метод в разделе выплат:
GET /api/v1/payouts/{payout_id}
В обоих параметрах принимается либо payment_id, который вернул PAYNEX, либо ваш
merchant_payment_id. Каждый метод проверяет не только текущего мерчанта, но и направление:
Pay-In нельзя получить через payout-метод и наоборот. Чужой, отсутствующий или относящийся
к другому направлению ID возвращает одинаковый 404.
curl https://paynex.work/api/v1/payments/order_98765 \
-H "Authorization: Bearer $KEY"
{
"payment_id": "de09a768-28ff-4b91-920e-3d6c27f4cfb6",
"merchant_payment_id": "order_98765",
"direction": "payin",
"status": "completed",
"amount_kopecks": 500000,
"currency": "RUB",
"payment_method": "sbp",
"created_at": "2025-01-15T07:57:28Z",
"updated_at": "2025-01-15T08:02:10Z",
"expires_at": "2025-01-15T08:27:28Z",
"completed_at": "2025-01-15T08:02:10Z"
}
Типичный Pay-In:
pending → matching → awaiting_payment → awaiting_verification → completed
↘ failed / expired / cancelled
Pay-Out не переходит в awaiting_verification: при N:1 он может возвращаться из
awaiting_payment в matching, если отдельная часть освобождена, и завершается только
после подтверждения всех частей. Его терминальные неуспешные состояния — failed или
expired.
| Статус | Значение |
|---|---|
pending |
заявка создана |
matching |
ищем встречную заявку в очереди |
awaiting_payment |
пара найдена, ждём перевод от отправителя |
awaiting_verification |
Pay-In: чек загружен, идёт проверка (авто/оператор) |
completed |
успех: перевод подтверждён |
failed |
провал (поддельный чек, отказ оператора и т.п.) |
expired |
истёк таймер ожидания |
cancelled |
Pay-In отменён клиентом со страницы оплаты до загрузки чека |
Зачисляйте средства клиенту ТОЛЬКО по completed (приходит вебхуком). Статус
awaiting_verification означает, что чек ещё проверяется (см. §8 про антифрод).
Как работает N:1
Один Pay-In не делится между несколькими выплатами. Один Pay-Out может быть собран из нескольких Pay-In того же метода и валюты:
| Событие | Подтверждено по Pay-Out | remaining_kopecks |
|---|---|---|
| Создан Pay-Out 5 000 ₽ | 0 ₽ | 500000 |
| Подтверждён Pay-In 2 500 ₽ | 2 500 ₽ | 250000 |
| Подтверждён Pay-In 1 500 ₽ | 4 000 ₽ | 100000 |
| Подтверждён Pay-In 1 000 ₽ | 5 000 ₽ | 0 |
Pay-Out становится completed, когда остаток равен нулю и все его части подтверждены.
Если отдельный Pay-In отклонён, его сумма возвращается в остаток Pay-Out, а выплата снова
может ждать подходящий депозит.
5. Баланс
GET /api/v1/balance
curl https://paynex.work/api/v1/balance \
-H "Authorization: Bearer $KEY"
{
"model": "commission_account",
"currency": "USDT",
"balance_usdt_cents": 10345,
"accrued_usdt_cents": 12345,
"settled_usdt_cents": 2000,
"accounting_complete": true,
"unpriced_accruals": 0
}
PAYNEX не хранит кошелёк доступных средств мерчанта. Этот метод возвращает точный
комиссионный расчётный баланс: начисленная комиссия минус погашения, в центах USDT.
Если часть старой истории нельзя оценить без ручной сверки, accounting_complete=false,
а balance_usdt_cents и accrued_usdt_cents возвращаются как null.
6. Idempotency-Key
Для POST /api/v1/payments и POST /api/v1/payouts передавайте заголовок
Idempotency-Key (любая ваша уникальная строка на операцию). Повторный запрос с тем же
ключом вернёт тот же
ранее созданный платёж, без дублирования. Ключ действует в рамках конкретного endpoint:
повторяйте тот же метод, путь и JSON-тело. Повтор ключа с другим телом вернёт 409.
Используйте ключ при ретраях по таймауту сети и не переиспользуйте его для новой операции.
merchant_payment_id — дополнительный бизнес-инвариант, общий для Pay-In и Pay-Out.
Внутри одного мерчанта он уникален во всём пространстве операций: точный повтор
нормализованного JSON вернёт уже созданный платёж, а изменённое тело или попытка
использовать ID Pay-In для Pay-Out вернёт 409. У разных мерчантов одинаковые значения
допустимы. Рекомендуется всегда передавать одновременно и Idempotency-Key, и
merchant_payment_id: первый защищает транспортный ретрай endpoint, второй — вашу
бизнес-операцию даже при смене транспортного ключа.
7. Вебхуки (как настроить и получать результат)
Основной webhook можно установить прямо через API. Операция идемпотентна:
PUT /api/v1/webhook
curl -X PUT https://paynex.work/api/v1/webhook \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{"webhook_url":"https://merchant.example/webhooks/paynex"}'
GET /api/v1/webhook возвращает текущую настройку. Чтобы отключить основной webhook,
передайте {"webhook_url":null}. Тот же URL можно задать в ЛК → Настройки.
Для Pay-In основной URL можно переопределить полем webhook_url в
POST /api/v1/payments; POST /api/v1/payouts такого поля сейчас не имеет и использует
только основной webhook.
Если подходящего URL нет в момент события, событие не создаётся, не отправляется и позже
автоматически не восстанавливается. Текущий статус всегда можно запросить через
GET /api/v1/payments/{payment_id} для Pay-In или GET /api/v1/payouts/{payout_id}
для Pay-Out, а также посмотреть в ЛК → Транзакции.
Для перечисленных ниже событий мы шлём POST на ваш webhook URL с JSON-телом и подписью.
Webhook создаётся не для каждого промежуточного статуса: например, отдельных событий
payment.matching и payment.awaiting_verification сейчас нет. Подпись считается на
Sign Key (api_secret).
Webhook endpoint должен использовать https:// и стандартный порт 443. URL с логином/
паролем или fragment (#...) отклоняется при сохранении. Перед каждой доставкой PAYNEX
проверяет все полученные DNS-адреса; loopback/private/link-local адрес делает попытку
недопустимой. Перенаправления не выполняются: endpoint должен сразу ответить на исходный
URL. Время одной попытки и объём читаемого ответа ограничены; возвращайте короткий ответ
2xx.
Заголовки:
Content-Type: application/json; charset=utf-8
X-Paynex-Event-Id: <уникальный id события>
X-Paynex-Event-Type: payment.completed
X-Paynex-Timestamp: 1781162244
X-Paynex-Signature: <hex HMAC-SHA256>
Тело:
{
"event_id": "…",
"event_type": "payment.completed",
"created_at": "2025-01-15T08:00:00Z",
"payment": {
"id": "de09a768-…", "merchant_payment_id": "order_98765",
"direction": "payin", "status": "completed",
"amount_kopecks": 500000, "original_amount_kopecks": null,
"remaining_kopecks": null, "currency": "RUB", "payment_method": "sbp",
"external_metadata": {}, "expires_at": "…", "completed_at": "…", "created_at": "…"
}
}
Типы событий: payment.created, payment.awaiting_payment, payment.completed,
payment.failed, payment.expired, payment.cancelled.
Проверка подписи (ОБЯЗАТЕЛЬНО)
Подпись = HMAC_SHA256(api_secret, "{X-Paynex-Timestamp}.{сырое_тело_запроса}").
Сравнивайте с заголовком X-Paynex-Signature. Проверяйте, что timestamp не старше 5 минут
(защита от replay). Тело берите как пришло, побайтово (не пересериализуйте).
import hmac, hashlib, time
def verify(api_secret: str, headers: dict, raw_body: bytes) -> bool:
ts = headers["X-Paynex-Timestamp"]
if abs(time.time() - int(ts)) > 300: # старше 5 минут — отклоняем
return False
expected = hmac.new(
api_secret.encode(), f"{ts}.".encode() + raw_body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, headers["X-Paynex-Signature"])
Идемпотентность и ретраи
- Обрабатывайте события идемпотентно по
event_id— мы можем доставить одно событие повторно. - По умолчанию делаем до 8 попыток: первая сразу, следующие после
5, 30, 120, 600, 1800, 7200, 21600секунд. Ответьте2xxкак только надёжно приняли событие.
8. Антифрод — что важно знать мерчанту
Чек о переводе технически невозможно на 100% отличить от хорошей подделки по самому файлу. Поэтому решение об одобрении опирается на поведение и историю, а не только на чек:
- Trust-тиры (гейтинг по истории). Чем меньше у клиента ПОДТВЕРЖДЁННЫХ депозитов, тем
ниже потолок суммы для авто-одобрения. Крупный депозит от нового клиента уйдёт на
ручную проверку (
awaiting_verificationдольше), итог — вебхуком. Это бьёт по экономике фрода: прокачка доверенного аккаунта стоит денег и времени. - Передавайте
player_full_name(из вашего KYC) — это включает сверку с ФИО отправителя на чеке и усиливает анти-треугольник. - Дроп-карты / velocity / повторное использование чека — ловятся автоматически.
- Привяжите финальное зачисление к
completed. Тогда автоматическая и ручная проверка обрабатываются в интеграции одинаково.
9. Лимиты и ошибки
- Rate limit: ~100 запросов/мин с одного IP и ~200/мин на мерчанта (на создание).
Превышение →
429. - Коды:
200/201ок;400бизнес-ошибка;401/403авторизация;409конфликтIdempotency-Keyилиmerchant_payment_id;422валидация тела (напр. для выплаты не передан реквизит под метод);429лимит;501фича не поддержана;503временная недоступность защитного лимитера.
10. Быстрые ответы перед запуском
| Вопрос | Ответ |
|---|---|
| Когда фиксировать успешный депозит? | После completed: этот статус означает готовый финансовый результат. |
| Как получить результат без webhook? | Запросить статус методом нужного направления и проверить журнал в ЛК. |
| Как работает матчинг N:1? | Один Pay-Out можно собрать из нескольких Pay-In; каждый Pay-In используется целиком. |
| Как начинается поиск пары? | Операция сразу переходит в matching и ждёт совместимую заявку в пределах TTL. |
| Что показывает баланс? | Комиссионные начисления за вычетом погашений в центах USDT. |
| Как настроить URL для событий? | Основной URL принимает Pay-In и Pay-Out; отдельный URL можно передать для конкретного Pay-In. |
Перед боевым трафиком проверьте успешный Pay-In, Pay-Out, N:1, истечение заявки,
повтор HTTP-запроса и повторную доставку webhook. В каждом сценарии сверяйте фактический
amount_kopecks, направление и финальный статус.
11. Песочница (dev)
- Swagger (интерактивный API и динамические примеры кода):
https://paynex.work/docs. - ReDoc (готовые примеры на cURL, Python, PHP, JavaScript и Go):
https://paynex.work/redoc. - Тестовый ключ и доступ к песочнице выдаём при подключении. Детали антифрод-проверок раскрываем индивидуально на этапе интеграции.