RSPAY API

RSPAY · v1

RSPAY API — приём платежей

Вы выставляете счёт, отдаёте плательщику ссылку, а деньги приходят на ваш кошелёк в сервисе за вычетом комиссии. Платить можно криптовалютой, с кошелька в приложении или по СБП. Платёжная страница открывается внутри Telegram-приложения.

Базовый URL
https://exchange.riskscan.net/api/rspay/v1

Формат — JSON, UTF-8. Суммы всегда строки с десятичной точкой ("100", "12.5"), валюта счёта — USDT. Время — ISO 8601 (UTC).

Подключение

  1. Откройте бота → «Профиль» → RSPAY → «Подключить». Подключение доступно всем.
  2. «API и вебхуки» → создайте ключ (он показывается один раз) и, при желании, укажите адрес вебхука.
  3. Либо выставляйте счета вручную: «Выставить счёт» в том же разделе, без API.

Авторизация

Ключ вида rsp_live_… передаётся в заголовке Authorization: Bearer <ключ> или X-API-Key: <ключ>. У нас ключ хранится только в виде хеша; потерянный ключ отзывается в приложении и создаётся новый. Одновременно может быть до 5 активных ключей.

Лимит — 120 запросов в минуту на ключ. При превышении ответ 429 с заголовком Retry-After.

Создать счёт

POST/invoices
тело запроса
{
  "amount": "100",
  "title": "Заказ №104",
  "description": "Два товара",
  "external_id": "ORD-104",
  "payer_telegram_id": "5512340987",
  "lifetime_sec": 86400
}
ПолеОбязательноОписание
amountдаСумма в USDT, число или строка.
titleдаНазвание, до 120 символов.
descriptionнетОписание, до 500 символов.
external_idнетВаш номер заказа (A–Z a–z 0–9 . _ : -, до 64). Повторный запрос с тем же номером вернёт тот же счёт (200), с другими данными — 409.
payer_telegram_idнетTelegram ID плательщика. Если указан, оплатить сможет только он. Если нет — счёт достанется тому, кто первым откроет ссылку.
lifetime_secнетСрок жизни, от 300 до 604800 секунд (по умолчанию сутки).

Заголовок Idempotency-Key работает как external_id, если тот не передан. Свой счёт оплатить нельзя: мерчант не может быть плательщиком.

ответ 201
{
  "invoice": {
    "id": "210",
    "status": "created",
    "amount": "100",
    "fee": "1",
    "net_amount": "99",
    "currency": "USDT",
    "title": "Заказ №104",
    "description": "Два товара",
    "external_id": "ORD-104",
    "payer_telegram_id": null,
    "pay_url": "https://t.me/<бот>?startapp=serviceOrder_<токен>",
    "created_at": "2026-10-04T16:00:00.000Z",
    "expires_at": "2026-10-05T16:00:00.000Z",
    "paid_at": null,
    "payment_method": null,
    "credited": null,
    "network_fee": null
  }
}

pay_url вставляйте в кнопку вашего бота или отправляйте плательщику: ссылка открывает приложение с оплатой. Комиссия fee фиксируется в момент создания счёта и дальше не меняется. После оплаты криптой в credited видно, сколько реально зачислено вам в монете платежа, а в network_fee — какую комиссию сети удержали.

Получить, список, отмена

МетодОписание
GET /invoices/{id}Один счёт.
GET /invoices?status=&limit=&before_id=&external_id=Список, новые сверху. Постранично по next_before_id. По external_id находится ваш заказ.
POST /invoices/{id}/cancelОтменить неоплаченный счёт. Оплаченный отменить нельзя (409); возврат вы делаете сами из кошелька.
GET /meВаша комиссия и лимиты счёта.

Статусы счёта

СтатусЗначение
createdВыставлен, плательщик ещё не открыл ссылку.
awaiting_paymentПлательщик известен, ждём оплату.
under_reviewПлатёж получен и проходит проверку (AML, а у оплаты по СБП — ещё и сверка ФИО плательщика с KYC). После неё счёт станет paid сам.
paidОплачен, деньги зачислены вам.
expiredСрок вышел без оплаты.
cancelledОтменён.

payment_method: wallet (с кошелька), crypto (ончейн), sbp (по СБП; сумма по СБП зачисляется с заморозкой на сутки, T+1).

Оплата по СБП доступна только плательщикам с пройденным KYC, и платить нужно с личной карты на своё ФИО. Если банк плательщика передал ФИО и оно не совпадает с KYC, платёж уходит на ручную проверку: счёт в статусе under_review, деньги вам не зачисляются до решения поддержки.

Вебхуки

Укажите адрес в разделе «API и вебхуки» (только https, публичный адрес) — мы пришлём POST с JSON.

тело вебхука
{
  "id": "evt_91",
  "type": "invoice.paid",
  "created_at": "2026-10-04T16:20:00.000Z",
  "data": { "invoice": { "id": "210", "status": "paid", "external_id": "ORD-104" } }
}

События: invoice.paid, invoice.under_review, invoice.expired, invoice.cancelled. Повторы доставки возможны — обрабатывайте идемпотентно (по id события). invoice.paid может прийти после invoice.under_review через часы. Ориентируйтесь на invoice.paid.

Ответьте любым кодом 2xx в течение 10 секунд. Иначе мы повторим доставку через 1 мин, 5 мин, 30 мин, 2 ч, 6 ч, 12 ч, 24 ч (всего 8 попыток). Кнопка «Отправить тест» в приложении шлёт событие ping.

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

Заголовок X-RSPAY-Signature: t=<unix-секунды>,v1=<hex>, где v1 = HMAC-SHA256(секрет, "<t>.<тело как пришло>"). Проверяйте по сырому телу и отвергайте запросы со старой меткой t (например, старше 5 минут). Секрет показывается в разделе «API и вебхуки». Дополнительно приходят X-RSPAY-Event (тип) и X-RSPAY-Delivery (номер события).

Node.js
const crypto = require('crypto');

// rawBody — тело запроса КАК ПРИШЛО (строка/Buffer), не пересобранный JSON
function verify(rawBody, header, secret, toleranceSec = 300) {
  const m = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(header || '');
  if (!m) return false;
  if (Math.abs(Date.now() / 1000 - Number(m[1])) > toleranceSec) return false;
  const expected = crypto.createHmac('sha256', secret).update(`${m[1]}.${rawBody}`).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(m[2]));
}
Python
import hmac, hashlib, re, time

def verify(raw_body: bytes, header: str, secret: str, tolerance=300) -> bool:
    m = re.fullmatch(r"t=(\d+),v1=([0-9a-f]{64})", header or "")
    if not m or abs(time.time() - int(m[1])) > tolerance:
        return False
    signed = m[1].encode() + b"." + raw_body
    expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, m[2])

Ошибки

формат
{ "error": { "code": "amount_too_small", "message": "…", "details": { "min_amount": "1" } } }
HTTPКоды
400invalid_amount, amount_too_small, invalid_title, invalid_description, invalid_external_id, invalid_payer, invalid_lifetime, invalid_status, invalid_request
401unauthorized
403merchant_blocked
404invoice_not_found, not_found
409external_id_conflict, too_many_open_invoices, invoice_already_paid, invoice_has_payment
429rate_limited
500internal_error

Деньги и безопасность

  • Деньги зачисляются вам на кошелёк в той монете и сети, которой заплатил покупатель. Из суммы вычитаются комиссия RSPAY (процент, но не менее минимума за платёж) и, при оплате криптой, комиссия сети за перевод монет в общий пул.
  • Каждый входящий платёж проходит AML-проверку. Пока проверка не пройдена, деньги вам не зачисляются, счёт в статусе under_review. Адрес, на который пришли непроверенные монеты, больше не выдаётся под оплаты.
  • Выводить заработанное можно как обычно из кошелька, в любой доступной сети; адрес вывода также проверяется.
  • Храните ключ и секрет вебхука на сервере. Если они попали не туда — отзовите ключ и перевыпустите секрет в приложении.

Как проверить

  1. Создайте ключ в «Профиль → RSPAY → API и вебхуки» и положите его в переменную RSPAY_KEY.
  2. Проверьте ключ: GET /me должен вернуть вашу комиссию и лимиты.
  3. Выставьте тестовый счёт на 1 USDT.
  4. Откройте pay_url с другого Telegram-аккаунта и оплатите (себе оплатить нельзя).
  5. Убедитесь, что GET /invoices/{id} вернул paid, а на вашем кошельке появилась сумма за вычетом комиссии.
первый счёт
curl -X POST https://exchange.riskscan.net/api/rspay/v1/invoices \
  -H "Authorization: Bearer $RSPAY_KEY" \
  -H "Content-Type: application/json" \
  -d '{"amount":"1","title":"Тестовый счёт","external_id":"TEST-1"}'