English Version 🇬🇧

JetBot API v1.0

Документация по интеграции для партнеров.

Production Base URL: https://api.jetbot.pw
Sandbox Base URL: https://dev.jetbot.pw (для тестов)

1. Подключение и Аутентификация

Для работы с API необходимо выполнить следующие шаги:

  1. Получить Bearer Token: Обратитесь к администратору JetBot для создания учетной записи API и получения токена доступа.
  2. Сгенерировать ключи RSA: Вам потребуется пара ключей RSA (4096 бит) для подписи запросов.
    • Приватный ключ (Private Key) остается у вас и используется для генерации подписи.
    • Публичный ключ (Public Key) необходимо передать администратору для верификации ваших запросов.
  3. Whitelist IP: Предоставьте статический IP-адрес вашего сервера, с которого будут отправляться запросы. Доступ к API разрешен только с доверенных IP.

Важно: Баланс мерчанта в API напрямую связан с балансом вашего Telegram-аккаунта в боте. Для пополнения баланса необходимо произвести стандартную операцию пополнения (депозит) через Telegram-бота: @jetpaycryptobot.

Важно: Для Sandbox (песочницы) используются отдельные учетные данные, токены и URL. Запрашивайте доступ к песочнице отдельно для безопасного тестирования интеграции.

Заголовки запроса

Все запросы к API должны содержать HTTP-заголовок авторизации:

Authorization: Bearer YOUR_TOKEN

2. Формирование подписи (Signature)

Методы, изменяющие состояние (например, создание выплаты), требуют обязательного параметра signature для защиты от подделки запросов.

Алгоритм создания подписи RSA-SHA512:

  1. Соберите все параметры тела запроса (JSON или Form-data) в словарь.
  2. Исключите поле signature из этого словаря, если оно там есть.
  3. Отсортируйте параметры по ключам (именам параметров) в алфавитном порядке (A-Z).
  4. Соберите строку для подписи в формате: METHOD_PATH?key1=value1&key2=value2.
    Пример строки: /api/payout?amount=1000&order_id=123&recipient=4444555566667777
    Обратите внимание: значения параметров должны быть такими же, как в отправляемом запросе.
  5. Подпишите полученную строку своим Приватным ключом (Private Key) используя алгоритм хеширования SHA512.
  6. Закодируйте полученную бинарную подпись в строку формата Base64.
  7. Передайте эту строку в параметре signature в теле запроса.

Пример кода на Python:

from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.primitives.asymmetric import padding
import base64

def sign_request(method_path, data, private_key_path):
    # 1. Сортировка и сборка строки
    sorted_keys = sorted([k for k in data.keys() if k != 'signature'])
    params = [f"{k}={data[k]}" for k in sorted_keys]
    data_string = f"{method_path}?{'&'.join(params)}"
    
    # 2. Загрузка ключа
    with open(private_key_path, "rb") as key_file:
        private_key = serialization.load_pem_private_key(
            key_file.read(), password=None
        )
        
    # 3. Подпись
    signature = private_key.sign(
        data_string.encode(),
        padding.PKCS1v15(),
        hashes.SHA512()
    )
    
    return base64.b64encode(signature).decode()

3. Методы API

GET /api/balance

Получение текущего баланса вашего мерчант-аккаунта в USDT.

Пример ответа:

{
  "balance": 5400.50,
  "currency": "USDT"
}
GET /api/rate

Получение актуального курса USDT/RUB для вашего мерчант-аккаунта. Подпись не требуется.

Пример ответа:

{
  "rate": "71.82"
}

Поле rate — строка с двумя знаками после точки (разделитель — точка), рублей за 1 USDT.

POST /api/payout

Создание новой заявки на выплату средств на банковскую карту (в рублях).

Параметр Тип Обязательно Описание
amount Float/Int Да Сумма выплаты в рублях (RUB). Сейчас: от 110 до 400 000 RUB.
recipient String Да Номер банковской карты получателя (только цифры, без пробелов, 16 знаков).
order_id String Да Уникальный идентификатор заказа в вашей системе. Повторный POST с тем же order_id (для этого мерчанта) возвращает 400 Duplicate order_id и не создаёт вторую выплату. Также используется в GET статуса.
phone String Нет Номер телефона получателя в формате 79xxxxxxxxx (11 цифр). Если не указан, используется заглушка.
name String Нет Имя получателя (латиница или кириллица). Полезно для некоторых банковских шлюзов.
signature String Да RSA-SHA512 подпись запроса (см. раздел "Формирование подписи").

Пример успешного ответа:

{
  "status": "pending",
  "id": "provider_abc123",  // ID транзакции в процессинговом шлюзе
  "order_id": "my_order_001",
  "payout_id": 42,         // Внутренний ID выплаты в JetBot
  "debited_amount_usdt": 20.88554616, // Списано с баланса в USDT
  "rate_used": 71.82       // Применённый курс RUB/USDT
}

debited_amount_usdt — точная сумма USDT, списанная с баланса при создании выплаты. Формула: amount / rate_used. Эти поля также возвращаются при запросе статуса выплаты.

POST /api/payout/sbp

Создание новой заявки на выплату через СБП (Систему быстрых платежей) — по номеру телефона и банку получателя, без номера карты. Это отдельный метод; /api/payout и его поведение не меняются.

Особенности СБП-выплат:

Подпись: формируется так же, как для /api/payout, но путь метода — /api/payout/sbp (пример строки: /api/payout/sbp?amount=5000&bank=Сбербанк&name=Иван Иванов&order_id=123&phone=79001234567).

Параметр Тип Обязательно Описание
amount Float/Int Да Сумма выплаты в рублях (RUB). Сейчас: от 110 до 20 000 RUB.
bank String Да Название банка получателя (например, «Сбербанк», «Тинькофф»). 2–50 символов.
name String Да Имя получателя (2–100 символов после trim). Значение - или пустая строка — ошибка Invalid recipient name.
phone String Да Номер телефона получателя (российский мобильный, формат 79xxxxxxxxx; ввод с 8 или +7 нормализуется автоматически).
order_id String Да Уникальный идентификатор заказа. Повтор с тем же значением → 400 Duplicate order_id.
signature String Да RSA-SHA512 подпись запроса (путь метода — /api/payout/sbp).

Пример успешного ответа:

{
  "status": "created",      // СБП-выплата принята в обработку
  "id": "19E3BABE0796332",  // Публичный ID (используется для проверки статуса и чека)
  "order_id": "my_order_001",
  "payout_id": 42,          // Внутренний ID выплаты в JetBot
  "debited_amount_usdt": 69.61849067,
  "rate_used": 71.82
}
POST /api/payout/batch

Пакетное создание нескольких выплат (карта и/или СБП) одним запросом. Методы /api/payout и /api/payout/sbp не меняются. Максимум 50 заявок в батче.

Подпись: путь /api/payout/batch. Параметры batch_id и payouts; значение payouts в строке подписи — компактный JSON с отсортированными ключами объектов.

Пример тела:

{
  "batch_id": "ORDER-124582",
  "payouts": [
    {
      "order_id": "ORDER-124582-P1",
      "type": "card",
      "amount": 75000,
      "recipient": "5469400023087641",
      "name": "Иван Иванов"
    },
    {
      "order_id": "ORDER-124582-P2",
      "type": "sbp",
      "amount": 15000,
      "phone": "79991234567",
      "bank": "Сбербанк",
      "name": "Пётр Петров"
    }
  ],
  "signature": "BASE64_SIGNATURE"
}

Поле typecard или sbp. Остальные поля элемента — как у одиночных методов (без signature внутри элемента).

Ответ: status батча — accepted / partial / rejected. Ошибка по одной заявке не откатывает уже созданные.

{
  "batch_id": "ORDER-124582",
  "status": "accepted",
  "payouts": [
    {
      "order_id": "ORDER-124582-P1",
      "status": "pending",
      "payout_id": 101,
      "id": "19E3BABE0796332",
      "debited_amount_usdt": 10.12,
      "rate_used": 80.5
    },
    {
      "order_id": "ORDER-124582-P2",
      "status": "created",
      "payout_id": 102,
      "id": "A1B2C3D4E5F6071",
      "debited_amount_usdt": 2.01,
      "rate_used": 79.7
    }
  ]
}
GET /api/payout/<ID>

Получение актуального статуса выплаты. В качестве <ID> можно использовать:

GET 404 и ошибки создания: если POST /api/payout или POST /api/payout/sbp вернул HTTP 400 / 401 / 402 / 403, заявка не создана (исключение: SBP daily phone request limit exceeded — см. таблицу). Повторный GET /api/payout/<order_id> в этом случае будет 404 Payout not found. Это не «в обработке» и не зависание: опрашивать 404 бессмысленно. Исправьте тело запроса и создайте выплату заново с новым order_id (или тем же, если выплата так и не создалась).

Пример ответа:

{
  "id": 42,
  "order_id": "my_order_001",
  "amount": 1500.00,
  "status": "confirmed",     // Возможные статусы: confirmed, pending, failed, cancelled
  "jetbot_status": "success", // Детальный статус шлюза: success, process, error и т.д.
  "payout_type": "card",     // Тип выплаты: "card" (на карту) или "sbp" (СБП)
  "recipient": "4276....1234", // Для СБП — реквизиты в 3 строки: имя, банк, телефон
  "created_at": "2023-10-27 14:30:00",
  "debited_amount_usdt": 20.88554616, // Списано с баланса в USDT
  "rate_used": 71.82       // Применённый курс RUB/USDT
}
GET /api/payout/<public_id>/receipt

Скачивание PDF-чека по успешной выплате. В качестве <public_id> используется ID с чека (поле id, возвращаемое при создании выплаты). Внутренний номер выплаты (payout_id), order_id и другие идентификаторы для скачивания чека не принимаются. Подпись не требуется.

Ответ — файл в формате PDF (Content-Type: application/pdf). Время на чеке указано по московскому времени (МСК, UTC+3).

Пример запроса (cURL):

curl -H "Authorization: Bearer <TOKEN>" \
     -o receipt.pdf \
     https://api.jetbot.pw/api/payout/358472DC4B20E6A/receipt

Возможные ошибки:

4. Коды ошибок

Формат JSON при ошибке (все методы, кроме успешного PDF-чека):

{
  "error": "текст из таблиц ниже",
  "code": 400
}

HTTP-статус ответа совпадает с полем code. При ошибках создания выплаты дополнительно может быть order_id. Успешный ответ (HTTP 200) поля code не содержит.

Тексты error ниже — точные строки из API. Сверяйте по равенству строки, а не по подстроке «ошибка валидации».

4.1. HTTP-коды (сводка)

HTTPКогдаПовторять запрос?
400Валидация тела, дубль order_id, суточный лимит запросов СБП по телефонуНет, пока не исправите данные. Дубль — не создавайте заново, берите payout_id/id из ответа
401Нет/неверный Bearer tokenНет, пока не исправите заголовок
402Недостаточно USDT на балансе мерчантаПосле пополнения, с новым order_id если выплата не создалась
403IP не в whitelist, неверная RSA-подпись, СБП выключен для аккаунта, чужая выплатаНет, пока не исправите доступ/подпись
404Выплата не найдена (в т.ч. после неуспешного POST — заявка не создана)Не опрашивать «до появления». Создайте заново только если POST не был 200
409Чек: выплата не в успешном статусеЧек — только после successful
500Внутренняя ошибка при создании / генерации чекаОсторожный retry; проверьте GET по order_id, не создалась ли выплата
503Nginx: больше 10 запросов/сек с IP (burst 20)Да, с экспоненциальной задержкой

4.2. Все методы — аутентификация и IP

Проверяются до разбора тела. Тела с подписью при 401/403 не обрабатываются.

HTTPerrorУсловие
401Missing or invalid Authorization headerНет заголовка или он не вида Bearer <token>
401Invalid tokenТокен неизвестен / отключён
403Access deniedIP клиента не в белом списке мерчанта (тот же текст на GET чужой выплаты)

4.3. POST /api/payout (карта)

Поля до бизнес-логики (Flask): amount, recipient, order_id, signature.

HTTPerrorУсловие
400No data providedПустое тело (нет JSON и form)
400Missing field: amount / recipient / order_id / signatureНет обязательного поля. Пустой order_id тоже даёт Missing field: order_id
400Duplicate order_idУ этого мерчанта уже есть выплата с тем же order_id. Тело дополнительно: order_id, payout_id, id (публичный ID), status (текущий клиентский статус). Новую выплату не создаём — используйте эти идентификаторы
400Invalid amountamount не число
400Amount must be at least 110 RUB. Provided: …Сумма < 110
400Amount must not exceed 400000 RUB. Provided: …Сумма > 400000
400Card number must contain exactly 16 digits. Provided: N digitsПосле удаления нецифровых символов не 16 цифр
400Invalid card number (failed Luhn check)16 цифр, не проходит Луна
402Insufficient balanceНе хватает USDT. Дополнительно: required, available. Выплата не создана
403Invalid signatureRSA-SHA512 не совпала (путь подписи /api/payout)
500Internal server errorНеобработанное исключение при создании

4.4. POST /api/payout/sbp

Обязательные поля: amount, bank, name, phone, order_id, signature.

HTTPerrorУсловие
400No data providedПустое тело
400Missing field: …Нет обязательного поля (в т.ч. пустой order_id)
400Duplicate order_idКак для карты: повтор того же order_id. В ответе payout_id, id, status
400Invalid amountamount не число
400Amount must be at least 110 RUB. Provided: …Сумма < 110
400SBP amount must not exceed 20000 RUB. Provided: …Сумма > 20000
400Bank name must be 2-50 charactersНазвание банка после trim короче 2 или длиннее 50 символов (после нормализации — до 80)
400Invalid recipient nameИмя после trim короче 2 или длиннее 100 символов. Типичный случай: name: "-" или пробелы. Выплата не создаётся. GET по order_id = 404. Не поллить.
400Invalid phone number (expected Russian mobile, e.g. 79001234567)Не российский мобильный (после нормализации не 11 цифр вида 79…)
400SBP daily phone request limit exceededНомер в чёрном списке суточного лимита запросов СБП. Заявка создаётся и сразу отменяется с возвратом средств. В ответе есть payout_id и id. GET по ним найдёт выплату со статусом failure. Повтор с тем же order_id даст Duplicate
402Insufficient balanceКак для карты
403Invalid signatureПуть подписи /api/payout/sbp
403SBP payouts are disabled for this accountСБП выключен мерчанту
500Internal server errorНеобработанное исключение

4.5. POST /api/payout/batch

Ошибки уровня запроса (ни одна заявка не разбирается):

HTTPerror
400No data provided
400Missing field: batch_id / payouts / signature
400batch_id must be non-empty
400payouts must be a JSON array (если передали строку, которая не JSON)
400payouts must be a non-empty array
400Too many payouts (max 50)
403Invalid signature (путь /api/payout/batch)

Если часть заявок валидна: HTTP 200, status: "partial", у ошибочных элементов status: "error" и те же error/code, что в §§4.3–4.4, плюс type must be 'card' or 'sbp', payouts[N] must be an object.

Если все заявки отклонены: HTTP 400 (или 402, если все ошибки — баланс; или 403, если все — запрет СБП), status: "rejected".

4.6. GET статуса и чека

МетодHTTPerrorУсловие
GET /api/payout/<ID>404Payout not foundНет выплаты с таким внутренним id / public id / order_id этого мерчанта
GET /api/payout/<ID>403Access deniedВыплата есть, но user_id другой
GET …/receipt404Payout not foundТолько поиск по публичному id с чека
GET …/receipt403Access deniedЧужая выплата
GET …/receipt409Receipt not ready, payout is still processingЕщё не success
GET …/receipt409Receipt is only available for successful payoutsТерминальный неуспех
GET …/receipt500Failed to generate receiptСбой генерации PDF

POST /api/payout/batch/status: те же принципы — No data provided, Missing field: signature, ids must be a JSON array / ids must be an array, Provide ids and/or batch_id, Too many ids (max 50), Invalid signature. В массиве результатов элемент может быть Empty id, Payout not found (code 404), Access denied.

4.7. Пример: дубль order_id

HTTP/1.1 400 Bad Request
{
  "error": "Duplicate order_id",
  "code": 400,
  "order_id": "my_order_001",
  "payout_id": 42,
  "id": "19E3BABE0796332",
  "status": "in_progress"
}

5. Ограничения (Rate Limits)

Для обеспечения стабильности работы API применяются следующие ограничения:

6. Webhooks (уведомления о статусах)

Вместо постоянного опроса метода GET /api/payout/<ID> вы можете получать push-уведомления при каждом изменении статуса выплаты. JetBot отправляет POST-запрос на указанный вами URL. Настройка (URL и секрет для подписи) выполняется на стороне JetBot — передайте их администратору.

Гарантия по терминальным статусам: уведомление со статусом failure отправляется только после окончательного отказа выплаты с возвратом средств. Если один платёжный шлюз отклонил выплату, а она была автоматически перенаправлена на другой шлюз, failure не отправляется. Можно безопасно строить бизнес-логику (например, возврат средств клиенту) на этом статусе.

Когда отправляется

Уведомление отправляется при смене клиентского статуса выплаты. Дублирующие уведомления с тем же статусом не отправляются.

statusЗначение
createdВыплата принята и создана.
in_progressВыплата в обработке шлюзом (в т.ч. при переборе/смене шлюзов).
successfulВыплата успешно завершена (терминальный статус).
failureВыплата окончательно отклонена, средства возвращены на баланс (терминальный статус).

Формат запроса

Метод POST, тело — JSON (Content-Type: application/json). Заголовки:

ЗаголовокОписание
X-Webhook-EventТип события, всегда payout.status_updated.
X-Webhook-IdУникальный ID события (event_id). Используйте для идемпотентности.
X-SignatureОпционально. Подпись тела запроса в формате sha256=<hex> (HMAC-SHA256). Отсутствует, если подпись не настроена.

Пример тела:

{
  "event": "payout.status_updated",
  "event_id": "9f1c2b7e4a8d4f6e9b0c1d2e3f4a5b6c",
  "status": "successful",       // created | in_progress | successful | failure
  "payout_id": "358472DC4B20E6A", // ID выплаты (как на чеке / поле id при создании)
  "order_id": "my_order_001",   // ваш merchant order_id
  "amount": 1500.00,
  "currency": "RUB",
  "payout_type": "card",
  "created_at": "2023-10-27 14:30:00",
  "timestamp": "2023-10-27T11:31:05Z" // UTC, момент отправки уведомления
}

Аутентификация по IP (по умолчанию)

По умолчанию вебхуки отправляются без подписи. Подлинность отправителя проверяется по IP-адресу: все уведомления приходят со статического адреса 185.207.14.225. Добавьте его в whitelist и принимайте вебхуки только с него.

Важно: проверяйте реальный IP TCP-соединения (socket / remote_addr), а не заголовки X-Forwarded-For / X-Real-IP — их можно подделать. Если ваш сервер стоит за обратным прокси (nginx, Cloudflare и т.п.), берите IP из доверенного источника прокси. Приём ведите только по HTTPS.

Проверка подписи (опционально)

Если для вашего аккаунта настроен секрет, каждое уведомление дополнительно подписывается. Подпись рассчитывается как HMAC-SHA256 от сырого тела запроса (в том виде, в котором оно получено, без переформатирования JSON) с использованием вашего секрета. Сравните результат со значением из заголовка X-Signature (уберите префикс sha256=). Всегда используйте сравнение с постоянным временем (constant-time). Если подпись не настроена, заголовок X-Signature не отправляется.

Пример проверки на Python (Flask):

import hmac, hashlib
from flask import request, abort

WEBHOOK_SECRET = b"your_webhook_secret"

@app.route("/jetbot/webhook", methods=["POST"])
def jetbot_webhook():
    raw = request.get_data()  # именно сырое тело, до парсинга JSON
    received = request.headers.get("X-Signature", "").removeprefix("sha256=")
    expected = hmac.new(WEBHOOK_SECRET, raw, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(received, expected):
        abort(403)

    event = request.get_json()
    # Идемпотентность: пропускаем уже обработанные event_id
    # if already_processed(event["event_id"]): return "", 200
    # ... обработка event["status"] ...
    return "", 200  # верните 2xx для подтверждения приёма

Подтверждение и повторные попытки