GoSwap Pay API
Платёжный шлюз для приложений в Telegram: принимайте деньги от своих пользователей и платите им — обе операции проводкой внутри шлюза, без выхода в блокчейн. Выплата пользователю бесплатна и мгновенна.
Введение
Используйте этот API на backend вашего приложения. Форма запросов намеренно повторяет Crypto Pay: если интеграция с ним уже написана, миграция — это замена базового URL и токена, а не переписывание кода.
Подключение — самообслуживание: кнопка GoPay в боте
@goswap создаёт организацию и выдаёт токен.
Счета и приём работают сразу; право на выплаты (transfer)
включается после разговора с нами.
Модель денег
Деньги, пришедшие от плательщиков, лежат на том же счёте, с которого уходят выплаты. Отдельная ликвидность под выплаты не нужна: оборот сам себя финансирует. Комиссия шлюза — ноль для всех: движение внутри — это проводка в базе, она нам ничего не стоит. Зарабатываем на выводе в цепь и обмене монет.
Три вещи, которые стоит понять до построения учёта:
У организации нет кошелька. «Счёт организации» — строка в наших книгах: сколько мы вам должны. Адрес, который получает ваш клиент для пополнения, нужен ровно для одного — зафиксировать, чей это платёж; деньги с него сметаются в общий кошелёк сервиса. Адрес опознаёт, кошелёк хранит, книги считают.
Кредита нет. Приложение тратит только то, что у него лежит. Попытка отправить больше — отказ, не отрицательный баланс.
Заработок забирается в боте. Кнопкой, владельцем организации. В API такого метода нет намеренно: токен, которым можно увести заработок на чужой личный баланс, — кража одним запросом.
Авторизация
Для запросов нужен заголовок:
Токен выдаётся при создании организации в GoPay и выглядит как
gsp_1a2b3c… — по префиксу его удобно искать в логах и коде,
если он куда-то попал. Перевыпустить утёкший токен можно кнопкой в боте:
старый умирает мгновенно. Только HTTPS: токен
в заголовке по обычному HTTP — это тот же токен, отданный любому по дороге.
transfer до включения). Формат ответов
Успех:
Ошибка — код HTTP и тело:
| Код | Когда |
|---|---|
| 401 | нет токена или он неизвестен |
| 403 | у приложения нет права на этот метод |
| 400 | неверные параметры, сумма вне предела или недостаточно средств |
| 422 | тело запроса не разобрано: нет обязательного поля, строка длиннее предела |
| 429 | слишком часто — лимит запросов в минуту на приложение (счета 60, чеки и переводы 20) или всплеск с одного IP; заголовок Retry-After говорит, через сколько секунд повторить. Тот же код — исчерпан суточный лимит переводов |
| 503 | временно недоступно — например, адрес для пополнения ещё готовится; заголовок Retry-After, если он есть, говорит, через сколько секунд повторить. При отказе ни адреса, ни окна не создаётся |
Повтор после 429 безопасен: ни счёт, ни перевод при отказе не создаются.
Перевод и чек идемпотентны по spend_id — повторный запрос с тем же
spend_id вернёт прежний результат и денег не сдвинет.
GETgetMe
Жив ли токен и что им можно. Первый вызов любой интеграции.
GETgetBalance
Сколько у приложения денег и в чём. Из этого платятся выплаты — опрашивайте перед крупной, или дождитесь уведомления о низком балансе.
POSTcreateInvoice
Счёт на пополнение. Пользователь платит — деньги падают на баланс приложения.
| Поле | Тип | Обяз. | Смысл |
|---|---|---|---|
| asset | строка | да | USDT, TRX, ETH, BNB |
| amount | строка | да | сумма, десятичной строкой. Пределы: USDT 100 000, TRX 1 000 000, BNB 200, ETH 50 — выше вернём 400 |
| description | строка | нет | что видит плательщик, до 1024 символов. Показывается как текст: HTML и скрипты в нём не исполняются нигде |
| payload | строка | нет | ваш номер заказа, до 4096 символов. Возвращается как прислали |
| expires_in | число | нет | секунд до истечения, по умолчанию 3600, от 60 до 2 678 400 |
Две ссылки на оплату — какую отправить плательщику, решаете вы.
bot_invoice_url— оплата в боте @goswap. Рекомендуем. Плательщик открывает чат GoSwap, и новый человек становится вашим рефералом: вы получаете долю с его операций.mini_app_invoice_url— платёжное окно поверх вашего бота, без перехода в чат. Быстрее для плательщика, но в бота GoSwap он попадает, только если сам разрешит — так устроен Telegram. Поэтому рефералов этот способ приводит меньше. Подходит, когда важна только оплата.
receipt_url — чек оплаты в боте GoSwap. Сделайте этой ссылкой номер счёта
в своём сообщении об оплате: клиент откроет чек и кошелёк GoSwap.
invoice_id — случайное десятизначное число, не порядковый
номер: по нему нельзя оценить, сколько счетов выставляют другие приложения.
Связывайте оплату со своей записью по payload или hash.
GETgetInvoices
Счета приложения, новые сверху.
| Параметр | Тип | Обяз. | Смысл |
|---|---|---|---|
| invoice_ids | строка | нет | номера счетов через запятую: 7304918265,5512093847. Не больше 500 |
| status | строка | нет | active, paid или expired |
| asset | строка | нет | монета счёта: USDT, TRX… |
| count | число | нет | сколько счетов вернуть, от 1 до 1000 (больше — вернём 1000). По умолчанию 100, с invoice_ids — все найденные |
| offset | число | нет | сколько самых новых счетов пропустить, по умолчанию 0 |
Покупатель нажал «Я оплатил» — спросите счёт по номеру.
getInvoices?invoice_ids=7304918265 вернёт ровно этот счёт с текущим
статусом. Так проверяют оплату, если вебхук не дошёл или ваш сервер не отвечал
в момент оплаты. По номеру находится любой счёт, даже если после него выставлено больше 1000 новых.
Сверяйте invoice_id и payload в ответе со своим заказом —
не берите items[0] вслепую.
expired — срок вышел, но это не окончательно: перевод,
пришедший после срока, может закрыть счёт — он станет paid, и придёт
вебхук invoice_paid.
Номер чужого или несуществующего счёта в ответ просто не попадает —
ошибки нет: пустой items значит, что такого счёта у вашего приложения нет.
invoice_ids сочетается со status и asset:
invoice_ids=…&status=paid вернёт только оплаченные из перечисленных.
Весь список — постранично: count=1000&offset=0,
count=1000&offset=1000, count=1000&offset=2000…
(шаг offset равен count), пока не придёт пустой items.
Архив листайте без status и фильтруйте у себя: счёт, сменивший статус
во время листания, со status может не попасть ни на одну страницу.
Новые счета сдвигают страницы — счёт может прийти дважды, сверяйте по invoice_id.
Ответ 400 с причиной: параметр не из таблицы выше, номер не из цифр, invoice_ids
без номеров, больше 500 номеров, invoice_ids[] или invoice_id
вместо invoice_ids, status не из списка, незнакомая монета
в asset, fiat (счетов в фиате нет), отрицательный offset.
Суммы — те же, что в вебхуке invoice_paid:
target — сколько выставлено, received — сколько
пришло всего, amount у оплаченного счёта — на какую сумму он
закрыт (номинал или, если счёт закрылся меньшей суммой, сколько пришло).
У открытого и истёкшего счёта amount — номинал.
POSTtransfer
Приложение платит своему пользователю. Главный метод.
| Поле | Тип | Обяз. | Смысл |
|---|---|---|---|
| user_id | строка | да | числовой Telegram id получателя |
| asset | строка | да | монета |
| amount | строка | да | сумма, не меньше 0.1 $ |
| spend_id | строка | да | ваш уникальный номер выплаты |
| comment | строка | нет | для вашей истории; получателю не показывается |
Получатель может ни разу не открывать бота. Telegram запрещает боту писать такому человеку первым, поэтому выплата ведёт себя двояко:
- человек уже открывал бота — деньги зачислены, ему ушло уведомление
«Выплата от «Ваша организация»»:
status: completed,notified: true; - человека у нас нет — деньги всё равно лежат на его id:
status: pending,notified: false. Покажите ему кнопку «Забрать» со ссылкойclaim_url: открыв бота по ней, он увидит сумму и от кого. Первое же открытие бота — по ссылке или любое другое — делает выплату полученной (completed).
claim_url есть в ответе всегда: строка в нашей базе не
гарантирует, что человеку можно написать (он мог заблокировать бота), а ссылка
работает из любого состояния. Ссылка адресная — открывший её с другого аккаунта
денег не получит. Пока выплата pending, её можно отозвать —
revokeTransfer.
already: true. Идемпотентность сильнее
суточного лимита: повтор выполненной выплаты не отнимает лимит.Право на переводы выдаётся отдельно и по умолчанию выключено:
утёкший токен с ним выносит весь баланс. Есть и суточный потолок в долларах.
spend_id привязан к вашему приложению — два приложения вправе
прислать одинаковый номер.
POSTrevokeTransfer
Отзыв невостребованной выплаты по вашему spend_id: деньги
возвращаются на счёт организации. Возможен только пока status: pending
— человек бота ещё не открыл. Открыл — деньги его, ответ 400.
| Поле | Тип | Обяз. | Смысл |
|---|---|---|---|
| spend_id | строка | да | номер выплаты, которую отзываете |
Повтор безопасен: already: true, денег не двигает.
spend_id после отзыва занят навсегда — новую выплату тому же
человеку делайте с новым номером. Суточный лимит отзыв не возвращает. Право
нужно то же, что на transfer.
GETgetTransfers
История выплат. Параметры: spend_id (можно через запятую),
count, offset. Нужен не для отчётности, а для
восстановления: потеряли ответ на transfer — спросите по своему
spend_id. Пусто — выплаты не было. У каждой выплаты статус:
pending — ждёт человека, её можно отозвать; completed —
получена; reverted — отозвана.
GETgetCurrencies
Что шлюз умеет.
can_withdraw: false — монету можно держать
и передавать внутри, но вывести в цепь нельзя. Не стройте на такой
монете экран вывода — ваш клиент узнает правду раньше вас.GETgetExchangeRates
Курсы к доллару.
Это справка, а не котировка сделки: значение из кеша
отчётности, живёт минутами. is_valid: false — биржа недоступна,
показано последнее известное. Считать по этому курсу деньги нельзя.
Вебхуки
Когда счёт оплачен, шлём POST на ваш адрес
(https, задаётся в GoPay). Каждый запрос подписан — проверяйте
подпись, она описана ниже.
target — сколько выставлено, received — сколько
пришло, amount — на какую сумму счёт оплачен: номинал, а если
пришло меньше — received.
amount, а не на сумму
вашего заказа: пополнение зачисляйте на amount, а товар
с ценой отдавайте, сверив с ней amount. Перевод на адрес счёта
(все сети, кроме TRON) закрывает счёт любой суммой от $0.5 — и в срок,
и после истечения.Переводы меньше $0.5 не зачисляются. BNB и ETH считаем в доллары по курсу; курса нет — счёт ждёт доплаты. В TRON счёт закрывает только полная сумма.
Ещё два события по счёту, с теми же полями:
invoice_partial— пришла часть, а счёт не закрыт (например, курса монеты нет).amount— этот перевод,received— всего;status—active, а если счёт уже истёк —expired.invoice_extra— перевод на уже закрытый счёт: деньги на балансе организации.amount— этот перевод,received— всего по счёту.
Другая монета на адрес счёта (например, BNB на счёт в USDT) зачисляется на баланс организации как пополнение, счёт не закрывает, вебхука о ней нет.
Подпись
Заголовок crypto-pay-api-signature — HMAC-SHA256 от
сырого тела на ключе sha256(ваш_токен):
Считайте от тела, как оно пришло, до разбора JSON: пересобранный из объекта JSON даст другие байты и другую подпись.
Повторы
Не ответили 2xx — повторим: 17 попыток за трое суток,
с растущими паузами.
invoice_id и молча отвечайте 200
на повтор. Начислить дважды по одному счёту — ваш убыток.Порядок внедрения
1. getMe — проверить токен.
2. createInvoice с тестовой суммой, оплатить самому, поймать вебхук.
3. Проверить подпись. Без проверки любой, кто узнал ваш адрес, начислит себе сколько захочет.
4. Попросить включить can_transfer и выставить суточный лимит.
5. transfer на свой же id, повторить с тем же spend_id —
убедиться, что деньги ушли один раз.
6. Развести обработку по payload и spend_id на своей стороне.
Чего в API нет намеренно
Овердрафта и отсрочки. Отрицательный баланс приложения — это наши деньги у тысячи его пользователей, взыскать которые не с кого.
Возврата полученного платежа. Пришедшее пользователю принадлежит
пользователю: отозвать можно только выплату, которую он ещё не получил
(revokeTransfer). Нужен возврат — сделайте его обычной выплатой
через transfer: он останется в истории обеих сторон.
Приёма фиата. Рубли — это лицензия и чарджбэки, а не ещё один метод оплаты. Шлюз работает с криптовалютой.