Merchant API

Base URL: https://api.paysoutsfi.io

Авторизация

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

ЗаголовокОписание
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

Создаёт заявку на пополнение или вывод. Если заявка с таким 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 или withdraw
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 для каждой новой заявки.

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.

POST /v1/requests/{request_id}/decide

Подтвердить или отклонить заявку на вывод. Доступно только для статуса pending (вывод ожидает решения).

Тело запроса

{
  "action": "approve",
  "reason": null
}
ПолеТипОписание
actionstringapprove или reject
reasonstringПричина отказа (опционально, для reject)

Ответ 200

{
  "request_id": 42,
  "status":     "approved",
  "action":     "approve"
}

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
}

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Вывод отклонён
Вебхук отправляется в фоне и не блокирует работу бота. Если ваш сервер недоступен — вебхук не повторяется, ориентируйтесь на GET /v1/requests/{id}.

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

Если задан 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}, POST /requests/{id}/decide, GET /transactions) статусы выводов нормализованы. В вебхуках приходят оригинальные значения.

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

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

Возможности

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

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

GET /v1/health

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

{ "ok": true }

Ошибки

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

api.paysoutsfi.io