Well Pay

Документация API

Приём платежей по СБП: вы создаёте счёт, отправляете покупателя на страницу оплаты, получаете уведомление о зачислении. Три запроса и один обработчик.

Порядок подключения

  1. 1. Зарегистрируйте компанию и создайте кассу в кабинете.
  2. 2. Выпустите тестовый ключ — он доступен сразу, до проверки компании. Отдельно выпустите ключ вебхуков на кассе: при создании подписки секрет показывается один раз, и потерять его нельзя — заведите его в коде сайта сразу.
  3. 3. Создайте счёт и проведите оплату в тестовом режиме.
  4. 4. Напишите обработчик уведомления и проверьте подпись секретом вебхуков — так вы узнаёте об оплате, а не по собственному опросу статуса.
  5. 5. После модерации переключите кассу в боевой режим и выпустите боевой ключ. На проде используйте именно его и адрес вашей боевой кассы: тестовый ключ создаёт только тестовые счета, на которые никто не может прислать настоящие деньги.

Базовый адрес

https://api.well-pay.pro

Спецификация OpenAPI

Машиночитаемое описание всего API — методы, схемы, коды ошибок и формат уведомлений с проверкой подписи. Годится и генератору клиентов, и языковой модели: отдайте файл целиком и попросите написать интеграцию под ваш стек.

Открыть openapi.json https://api.well-pay.pro/openapi.json

Авторизация

Каждый запрос — с заголовком X-API-Key. Ключ у каждой кассы свой.

X-API-Key: wp_live_ВАШ_КЛЮЧ

ВАШ_КЛЮЧ — не настоящий токен, а место под ваш: ключ выпускается в кабинете и выглядит как wp_live_… (бой) или wp_test_… (тест).

Где взять ключ

В кабинете: Кассы → нужная касса → Ключи API → Выпустить. Секрет показывается один раз — сохраните его сразу. Потеряли: выпустите новый, старый отзовите. Восстановить нельзя, у нас хранится только хеш.

Режим ключа важнее режима кассы

Ключ выпускается под конкретный режим: wp_test_… создаёт только тестовые счета, wp_live_… — только боевые. Ваша интеграция не начнёт внезапно принимать настоящие деньги из-за переключения тумблера в кабинете.

Создание счёта

POST /v1/invoices

Суммы — в копейках

Целые числа невозможно испортить округлением. 10000 — это 100 ₽.

В примере — wp_test_…: так удобнее пробовать. На проде подставьте свой wp_live_… и проверьте, что касса в боевом режиме.

curl -X POST https://api.well-pay.pro/v1/invoices \
  -H "X-API-Key: wp_test_ВАШ_КЛЮЧ" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 150000,
    "order_id": "order-10241",
    "description": "Заказ №10241",
    "success_url": "https://shop.ru/success",
    "fail_url": "https://shop.ru/fail"
  }'

Параметры

ПолеТипОписание
amountint Сумма заказа в копейках. Обязательное.
order_idstring Ваш номер заказа. Обязательное. Повтор вернёт уже созданный счёт.
descriptionstring Видно покупателю на странице оплаты.
success_urlstring Куда вернуть после оплаты. По умолчанию — из настроек кассы.
fail_urlstring Куда вернуть при отказе или истечении.
ttl_minutesint Сколько живёт счёт: от 5 минут до 5 суток.
payer_emailstring Контакт плательщика. Хранится зашифрованным.
metadataobject Ваши данные — вернём их в вебхуке без изменений.

Ответ 201 Created

{
  "id": "inv_3F8KQ2M1N7XBZ0RTVCWD9E",
  "order_id": "order-10241",
  "status": "pending",
  "mode": "test",
  "amount": 150000,
  "charged_amount": 150000,
  "fee": 18000,
  "net": 132000,
  "currency": "RUB",
  "checkout_url": "https://pay.well-pay.pro/i/xxxxxxxx",
  "qr_text": null,
  "expires_at": "2026-08-18T15:30:00+00:00",
  "paid_at": null
}

Повторный запрос безопасен

Если ответ не дошёл и вы повторили запрос с тем же order_id, вернётся тот же счёт и код 200 вместо 201. Второй счёт и двойное списание невозможны.

Отправьте покупателя на checkout_url — там уже есть QR, таймер и возврат в ваш магазин.

Хотите рисовать QR сами

Поле qr_text в ответе на создание почти всегда null: код выпускает банковская система, и занимает это несколько секунд. Мы запрашиваем его сразу, поэтому запросите счёт ещё раз через GET /v1/invoices/{id} — там код уже будет. Пока его нет, показывайте покупателю ожидание, а не ошибку.

Лимиты

Минимальная сумма10,00 ₽ Настраивается на кассе
Максимальная сумма50 000,00 ₽ Предел одного платежа по СБП
Срок жизни счёта5 суток По умолчанию 60 минут
ВалютаRUB Других пока нет

Сумма сверх лимита — это 422 с понятным текстом, а не отказ на стороне банка: счёт в таком случае не создаётся вовсе.

Статус счёта

GET /v1/invoices/{invoice_id}
GET /v1/orders/{order_id}
POST /v1/invoices/{invoice_id}/cancel

Второй метод пригодится, когда ответ на создание потерялся: вместо повторного создания спросите, что стало с заказом.

Статусы

pendingОжидает оплаты
paidОплачен, деньги получены
expiredИстёк срок
canceledОтменён

Вебхуки

Адрес обработчика задаётся на кассе. Там же при создании подписки один раз показывается секрет — им подписываются уведомления. Мы отправляем POST с JSON и ждём любой ответ 2xx. Не получили — повторим по расписанию 0, 1, 5, 30, 120, 360 минут.

{
  "id": "evt_3F8KQ2M1N7XBZ0RTVCWD9E",
  "event": "payment.succeeded",
  "created_at": "2026-08-18T15:04:11+00:00",
  "mode": "live",
  "data": {
    "invoice_id": "inv_3F8KQ2M1N7XBZ0RTVCWD9E",
    "order_id": "order-10241",
    "status": "paid",
    "amount": 150000,
    "charged_amount": 150000,
    "fee": 18000,
    "net": 132000,
    "currency": "RUB",
    "paid_at": "2026-08-18T15:04:10+00:00",
    "metadata": null
  }
}

Проверка подписи

Проверяйте её обязательно: без этого кто угодно, узнав ваш адрес, сможет прислать поддельное «оплачено».

X-WellPay-Signature: sha256=<hex>
X-WellPay-Timestamp: 1787060000
X-WellPay-Event: payment.succeeded
X-WellPay-Delivery: dlv_...

signature = HMAC_SHA256(secret, "{timestamp}.{сырое тело запроса}")

Секрет подписи — не API-ключ

В формуле выше secret — это секрет конкретной подписки на вебхуки, выданный при её создании на кассе. Ключ X-API-Key, которым вы создаёте счета, здесь не подойдёт: подпись не сойдётся, и все уведомления будут отброшены. У части провайдеров это одно и то же значение — у нас нет, чтобы утёкший ключ приёма уведомлений не давал доступа к созданию платежей.

Подписывается сырое тело

Считайте подпись до разбора JSON. Если сначала распарсить и собрать обратно, порядок ключей и пробелы изменятся — подпись не сойдётся.

import hmac, hashlib

def verify(secret: str, signature: str, timestamp: str, raw_body: bytes) -> bool:
    payload = f"{timestamp}.".encode() + raw_body
    expected = "sha256=" + hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)

События

payment.succeededПлатёж прошёл, деньги получены
payment.failedПлатёж не прошёл
invoice.expiredСчёт истёк
invoice.canceledСчёт отменён

Отвечайте быстро и дедуплицируйте

Долгая обработка приводит к повторным доставкам. Ответьте 200 сразу, а работу выполняйте фоном. Одно и то же событие может прийти дважды — сверяйтесь по полю id.

Тестовый режим

Тестовый ключ доступен сразу после регистрации, до проверки компании. Счета создаются как настоящие, на странице оплаты есть кнопка «Оплатить (тест)» — она проводит полный цикл вместе с вебхуком.

Тестовые платежи не попадают в баланс

Это не настоящие деньги: в реестре они помечены, в оборот и остаток не входят.

Ошибки

{"status": "failed", "error_message": "Минимальная сумма платежа — 10,00 ₽"}
КодЧто означает
401Ключ не передан, неверен или отозван
403Касса на паузе, компания не прошла модерацию или адрес не разрешён
404Счёт не найден
422Данные не прошли проверку — повторять бессмысленно
502Провайдер недоступен — повторите позже

Остались вопросы?

Напишите — поможем с интеграцией.

Написать в поддержку