Merchant API

Продакшен: https://api.paysoutsfi.io

Стейдж (тесты): https://api-stage.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

Создаёт заявку на пополнение (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 пользователя. Передайте 0, если пользователь ещё не запускал бота
account_idstringдаID аккаунта пользователя в вашей системе
amount_rubintдаСумма в рублях, больше 0
method_keystringдаМетод: sbp, card
metaobjectнетПроизвольные данные

Ответ 200

{
  "request_id":        42,
  "display_id":        "M18-000042",
  "merchant_deal_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-ответ.
До запуска бота. Если вам нужны наши ID и callback до нажатия пользователем Start, заранее вызовите POST /v1/requests с user_tg_id: 0. Заявка создастся в статусе waiting_user, а ответ сразу вернёт request_id и display_id. Если используется только диплинк, до создания заявки в боте callback отправить невозможно.

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 ID заявок

Два дополнительных метода возвращают соответствие между вашим merchant_request_id, нашим внутренним request_id и публичным ID вида M18-000042. Старые методы и форматы ответов не изменены.

/v1/requests/by-merchant-request/{merchant_request_id}

Поиск по ID заявки на вашей стороне. Для кассы с несколькими проектами можно передать необязательный query-параметр project_key. Если одинаковый ID найден в нескольких проектах без project_key, API вернёт 409.

/v1/requests/by-display-id/{display_id}

Обратный поиск по нашему публичному ID, например M18-000042.

Ответ 200

{
  "request_id":             42,
  "display_id":             "M18-000042",
  "merchant_deal_id":       42,
  "merchant_request_id":    "epic-order-123",
  "merchant_project_key":   "casino",
  "status":                 "waiting_user",
  "created_at":             "2026-08-08 12:00:00",
  "updated_at":             "2026-08-08 12:00:00"
}
Оба метода требуют обычную авторизацию мерчанта и возвращают только его собственные заявки.

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));
}

Поддерживаются оба домена Telegram. Основной вариант: telegram.me, резервный: t.me. Путь, имя бота и параметр start полностью одинаковы; при недоступности одного домена замените только имя домена.

Ссылка для перехода пользователя в бота с вашего сайта. Бот подставит account_id и при необходимости сумму. ID мерчанта в ссылке по желанию: если он есть — заявка и вебхуки идут к указанному мерчанту; если нет — используется логика бота без мерчанта в параметре (например общая привязка при старте).

Для точного сопоставления заявки можно передать четвёртым параметром merchant_request_id. Предварительно создавать такую заявку через API не требуется: ID сохраняется при создании заявки в боте и возвращается в callback и long-polling. Все старые форматы продолжают работать без изменений.

Разделитель — латинская a (ASCII). Если в ссылке по ошибке попала кириллическая «а», бот нормализует её при разборе. Нижнее подчёркивание _ в некоторых Telegram-клиентах склеивается с числами и искажает значения.
merchant_request_id может содержать латинские буквы, цифры, _ и -. Весь параметр start должен помещаться в ограничение Telegram — 64 символа.

Форматы

ФорматПримерmerchant_idaccount_idamountmerchant_request_id
{merchant_id}a{user_id}a{amount}a{merchant_request_id} 18a187428a5000aepic-order-123 ✅ 18✅ 187428✅ 5000 ₽✅ epic-order-123
{merchant_id}a{user_id}a{amount} 6a68595a2010 ✅ 6✅ 68595✅ 2010 ₽
{merchant_id}a{user_id} 6a68595 ✅ 6✅ 68595— вводится вручную
{merchant_id}_{user_id}_{amount} 14_68595_2010 ✅ 14✅ 68595✅ 2010 ₽
{user_id}_{amount} 68595_2010 ✅ 68595✅ 2010 ₽
{user_id} 68595 ✅ 68595— вводится вручную

Параметры

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

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

# Новый формат с merchant_request_id:
https://telegram.me/BOTUSERNAME?start={merchant_id}a{user_id}a{amount}a{merchant_request_id}
https://t.me/BOTUSERNAME?start={merchant_id}a{user_id}a{amount}a{merchant_request_id}

# Пример:
https://telegram.me/MatchCore_bot?start=18a187428a5000aepic-order-123

# Старый основной URL:
https://telegram.me/BOTUSERNAME?start={merchant_id}a{user_id}a{amount}

# Резервный URL с тем же payload:
https://t.me/BOTUSERNAME?start={merchant_id}a{user_id}a{amount}

# Пример с мерчантом и суммой:
https://telegram.me/BOTUSERNAME?start=6a68595a2010
https://t.me/BOTUSERNAME?start=6a68595a2010

# Только мерчант + игрок (сумма в боте):
https://telegram.me/BOTUSERNAME?start=6a68595
https://t.me/BOTUSERNAME?start=6a68595

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

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Невалидное тело запроса (отсутствует обязательное поле)

prod api.paysoutsfi.io · stage api-stage.paysoutsfi.io