STAGING Тестовый API. Здесь отдельная база и ключи; для боевой интеграции используйте продакшен. Документация прода: api.paysoutsfi.io/docs

Merchant API (staging)

Base URL для запросов: https://api-stage.paysoutsfi.io (в примерах подписи указывайте путь как на проде, например /v1/requests)

Авторизация

Каждый запрос требует четыре заголовка:

ЗаголовокОписание
X-Api-KeyИдентификатор ключа (key_id)
X-TimestampUnix-время в секундах. Допуск ±300 сек от времени сервера
X-NonceСлучайная строка, уникальная для каждого запроса (в течение 5 мин)
X-SignatureHMAC-SHA256 подпись (hex)

Строка для подписи

METHOD\nPATH\nTIMESTAMP\nNONCE\nBODY

Поля соединяются через символ перевода строки \n. Тело — точная строка байт запроса (без изменений).

Node.js

import crypto from "crypto";

function sign(secret, method, path, ts, nonce, body = "") {
  const msg = [method.toUpperCase(), path, String(ts), String(nonce), body].join("\n");
  return crypto.createHmac("sha256", secret).update(msg).digest("hex");
}

Python

import hmac, hashlib

def sign(secret, method, path, ts, nonce, body=""):
    msg = "\n".join([method.upper(), path, str(ts), str(nonce), body])
    return hmac.new(secret.encode(), msg.encode(), hashlib.sha256).hexdigest()
Каждый nonce принимается только один раз в течение 5 минут — защита от replay-атак.

POST /v1/requests

Создаёт заявку на пополнение (type: "deposit"). Если заявка с таким merchant_request_id уже существует — возвращает её без создания новой ("created": false).

Тело запроса

{
  "merchant_request_id": "order-12345",
  "type":                "deposit",
  "user_tg_id":          123456789,
  "account_id":          "player_42",
  "amount_rub":          1500,
  "method_key":          "card",
  "meta":                {"note": "опционально"}
}
ПолеТипОбяз.Описание
merchant_request_idstringдаУникальный ID заявки на вашей стороне (1–64 символа)
typestringдаdeposit (интеграция только для пополнений)
user_tg_idintдаTelegram ID пользователя
account_idstringдаID аккаунта пользователя в вашей системе
amount_rubintдаСумма в рублях, больше 0
method_keystringдаМетод: sbp, card
metaobjectнетПроизвольные данные

Ответ 200

{
  "request_id":  42,
  "status":      "waiting_requisites",
  "terminal_id": 10,
  "method_key":  "card",
  "amount_rub":  1500,
  "created":     true
}
Если "created": false — заявка уже существует, повторное уведомление трейдеру не отправляется. Используйте новый merchant_request_id для каждой новой заявки.
Ответ POST /v1/requests возвращается сразу после создания заявки. Исходящий webhook на ваш webhook_url (если задан) отправляется в фоне и не задерживает HTTP-ответ.

GET /v1/requests/{request_id}

Возвращает текущий статус заявки. Доступны только заявки вашего мерчанта.

Ответ 200

{
  "request_id":       42,
  "status":           "approved",
  "type":             "deposit",
  "terminal_id":      10,
  "method_key":       "card",
  "amount_rub":       1500,
  "commission_amount": 75.0,
  "requisites_sent":  true,
  "proof_submitted":  true,
  "created_at":       "2025-01-15 12:00:00",
  "updated_at":       "2025-01-15 12:05:30"
}
Статусы выводов нормализованы: withdraw_approvedapproved, withdraw_rejectedrejected, withdraw_pendingpending.

GET /v1/transactions

Список транзакций мерчанта с фильтрами по дате и пользователю.

Query-параметры

ПараметрТипПо умолчаниюОписание
date_fromstringНачало периода, формат YYYY-MM-DD
date_tostringКонец периода, формат YYYY-MM-DD
user_tg_idintФильтр по Telegram ID пользователя
limitint50Количество записей (1–500)
offsetint0Смещение для пагинации

Ответ 200

{
  "transactions": [
    {
      "request_id":        42,
      "type":              "deposit",
      "status":            "approved",
      "user_tg_id":        123456789,
      "account_id":        "player_42",
      "method_key":        "card",
      "amount_rub":        1500,
      "commission_amount": 75.0,
      "terminal_id":       10,
      "created_at":        "2025-01-15 12:00:00",
      "updated_at":        "2025-01-15 12:05:30"
    }
  ],
  "total": 156
}

PUT /v1/merchant/webhook

Сохранить webhook_url, webhook_secret (подпись X-Signature) и опционально webhook_key_id — значение для заголовка X-Api-Key исходящих POST на ваш сервер (если приёмник требует идентификатор ключа). Требуется та же авторизация, что и для остальных методов.

Тело запроса (JSON)

{
  "webhook_url":     "https://example.com/callback",
  "webhook_secret":  "секрет для проверки подписи",
  "webhook_key_id":  "key_live_…"
}

Каждый вызов записывает все три поля в БД: передайте актуальные webhook_url, webhook_secret и webhook_key_id; пустое значение или null для поля сбрасывает его. Чтобы не затереть Secret или Key ID при смене только URL, сначала прочитайте текущие настройки из кабинета бота или храните копию у себя. В боте: 🔗 Webhook — правка URL, Secret и Key ID по отдельности.


Webhooks

На каждое изменение статуса на ваш webhook_url отправляется POST-запрос. Набор полей одинаков для всех статусов.

Тело запроса

{
  "event":              "request.status_changed",
  "request_id":         42,
  "merchant_id":        1,
  "type":               "deposit",
  "user_tg_id":         123456789,
  "account_id":         "player_42",
  "amount_rub":         1500,
  "method_key":         "card",
  "terminal_id":        10,
  "payment_submethod":  "card",
  "commission_amount":  75.0,
  "created_at":         "2025-01-15 12:00:00",
  "status":             "approved"
}

При отклонении добавляется поле reason:

{
  "status": "rejected",
  "reason": "Чек не прошёл проверку",
  ...
}

Когда отправляется вебхук

Статус в вебхукеСобытие
waiting_requisitesЗаявка создана, ожидаются реквизиты
requisites_sentРеквизиты отправлены пользователю
proof_submittedПользователь отправил чек
approvedЗаявка принята
rejectedЗаявка отклонена (есть поле reason)
lockedЗаявка заблокирована трейдером на 24 часа
withdraw_pendingЗаявка на вывод создана
withdraw_approvedВывод подтверждён
withdraw_rejectedВывод отклонён
Вебхуки отправляются в фоне (в том числе после POST /v1/requests) и не блокируют HTTP-ответ API. Если ваш сервер недоступен — доставка не повторяется, ориентируйтесь на GET /v1/requests/{id}.

Заголовок X-Api-Key (опционально)

Если в настройках мерчанта задан webhook_key_id (в API — PUT /v1/merchant/webhook, в боте — поле Key ID в разделе Webhook), исходящий POST содержит заголовок X-Api-Key с этим значением — для приёмников, которые требуют идентификатор ключа отдельно от подписи.

Подпись вебхука

Если задан webhook_secret, к каждому запросу добавляются заголовки:

ЗаголовокОписание
X-TimestampUnix-время отправки
X-NonceСлучайная строка
X-SignatureHMAC-SHA256(secret, {ts}.{nonce}.{body})

Проверка подписи (Python)

import hmac, hashlib

def verify(secret, body_bytes, ts, nonce, sig):
    msg = f"{ts}.{nonce}.{body_bytes.decode()}".encode()
    expected = hmac.new(secret.encode(), msg, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, sig)

Проверка подписи (Node.js)

import crypto from "crypto";

function verify(secret, body, ts, nonce, sig) {
  const msg = `${ts}.${nonce}.${body}`;
  const expected = crypto.createHmac("sha256", secret).update(msg).digest("hex");
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig));
}

Ссылка для перехода пользователя в бота с вашего сайта. Бот автоматически привяжет заявку к вашему мерчанту и заполнит сумму.

Рекомендуемый разделитель — буква a. Нижнее подчёркивание _ в некоторых Telegram-клиентах склеивается с числами и искажает значения.

Форматы

ФорматПримерmerchant_idaccount_idamount
{merchant_id}a{user_id}a{amount} 6a68595a2010 ✅ 6✅ 68595✅ 2010 ₽
{merchant_id}a{user_id} 6a68595 ✅ 6✅ 68595— вводится вручную
{user_id}_{amount} 68595_2010 ✅ 68595✅ 2010 ₽
{user_id} 68595 ✅ 68595— вводится вручную

Параметры

ПараметрОписание
merchant_idВаш ID мерчанта. Если передан — вебхуки уходят на ваш webhook_url
user_idID пользователя в вашей системе — записывается в поле account_id заявки
amountСумма пополнения в рублях. Если не передана — пользователь вводит сам

Полная ссылка

https://t.me/BOTUSERNAME?start={merchant_id}a{user_id}a{amount}

# Пример:
https://t.me/BOTUSERNAME?start=6a68595a2010

Статусы заявок

Deposit

СтатусОписание
waiting_requisitesЗаявка создана, трейдер ещё не выдал реквизиты
requisites_sentРеквизиты отправлены пользователю
waiting_proofПользователь нажал «Оплатил», ожидается чек
proof_submittedЧек получен, ожидается решение трейдера
lockedЗаявка заблокирована на 24 часа (спорная ситуация)
approvedОплата подтверждена
rejectedЗаявка отклонена

Withdraw

Внутренний статусВ API (нормализованный)Описание
withdraw_pendingpendingСоздана, ожидает решения мерчанта
withdraw_approvedapprovedВывод подтверждён
withdraw_rejectedrejectedВывод отклонён
В ответах API (GET /requests/{id}, GET /transactions) статусы выводов (если такие заявки есть) нормализованы. В вебхуках приходят оригинальные значения.

Кабинет мерчанта в боте

После привязки Telegram-аккаунта к мерчанту открывается доступ к кабинету в боте через кнопку 🏪 Кабинет мерчанта.

Возможности

Кнопки кабинета

КнопкаДействие
📊 Сегодня / 📅 7 дней / 📋 ВсёСтатистика за выбранный период
🗂 ЗаявкиПоследние заявки (все статусы)
✅ Одобренные / ❌ ОтклонённыеФильтр в списке заявок
🔑 API ключиПоказать key_id и secret активных ключей
🔗 WebhookПросмотр и редактирование URL, Secret и Key ID

GET /v1/health

Проверка доступности API. Авторизация не нужна.

{ "ok": true }

Ошибки

КодПричина
400Некорректный запрос
401Отсутствуют заголовки, неверная подпись или повторный nonce
403Мерчант или API-ключ отключены
404Заявка не найдена или не принадлежит вашему мерчанту
409Нет подходящего терминала для суммы/метода
422Невалидное тело запроса (отсутствует обязательное поле)

staging: api-stage.paysoutsfi.io/docs/api · prod: api.paysoutsfi.io/docs