Каталог, покупки, заказы, баланс и уведомления по одному ключу: игровая валюта, подарочные карты, ключи игр, пополнение Steam, Telegram Stars и Premium.
Версия 1.0.0OpenAPI 3.1.0https://easydonat.com/api/v1
API даёт вашей программе то же, что кабинет партнёра: каталог с ценами, покупки, статусы заказов, баланс и уведомления о выполнении. Подходит для своего сайта, Telegram-бота или приложения — заказы выдаются автоматически.
JSON в UTF-8. Время — ISO 8601 в UTC: 2026-10-21T10:00:00.000Z.
Деньги
Доллары США, строкой: суммы — с 2 знаками ("12.50"), цена единицы (звезды, единицы валюты Steam) — до 9 знаков ("0.0176"). Считайте их как десятичные числа или в центах, не через дробные числа с плавающей точкой.
Оплата
Покупка сразу списывает деньги с баланса. Если заказ не выполнится, деньги вернутся на баланс — статус refunded.
Пять шагов от ключа до первого выполненного заказа — на примере пополнения игры.
1
Получите ключ
В кабинете партнёра откройте «Интеграция» и создайте ключ. Полный ключ показывается один раз — сохраните его на своём сервере, например в переменной окружения:
Передайте Idempotency-Key — тогда повтор запроса не купит второй раз, и max_price_usd — цену, выше которой покупать не нужно. Ключ в примере — для образца: подставьте номер заказа в вашей системе, для каждой новой покупки — новый ключ.
Ответ — HTTP 201 и заказ в статусе pending: деньги списаны, заказ выполняется. Оборвалась связь — повторите тот же запрос с тем же Idempotency-Key: вернётся тот же заказ, второго списания не будет.
5
Узнайте итог
Статус заказа — в GET /orders/{order_id} или в уведомлении order.completed на ваш адрес (вебхук).
Только в заголовке — никогда в адресе запроса: адреса попадают в журналы серверов. Если переданы оба заголовка, в них должен быть один и тот же ключ.
Храните ключ на своём сервере. Не кладите его в код сайта, мобильного приложения или бота, который видят пользователи.
Ключи создаются и отзываются в кабинете («Интеграция»), активных — до 10. Полный ключ виден только при создании: мы храним лишь его отпечаток, начало и последние 4 символа, поэтому потерянный ключ не восстановить — создайте новый, а старый отзовите. Отозванный ключ получает 401 unauthorized.
Смена пароля и выход из кабинета ключи не отзывают — иначе остановились бы ваши интеграции. Ключ, который мог утечь или больше не нужен, отзовите в «Интеграции».
В кабинете видно, когда ключ последний раз использовался (обновляется не чаще раза в минуту).
Если аккаунт партнёра отключён, не работает ни один ключ — 403 account_disabled.
Доступ по IP
Необязательная защита на случай утечки ключа: в кабинете можно перечислить до 20 адресов IPv4 и IPv6. Пока список не пуст, запросы с других адресов получают 403 ip_not_allowed.
Ответы и ошибки
Успешный ответ — { "ok": true, … }. Новый заказ — HTTP 201, повтор покупки с тем же Idempotency-Key — HTTP 200 и тот же заказ.
Ошибка — { "ok": false, "error": "…", "code": "…" }, у некоторых кодов — с дополнительными полями. Ветвитесь по code: текст error — пояснение по-русски для людей.
Ответ 402
{"ok":false,"error":"Недостаточно средств на балансе","code":"insufficient_funds","need_usd":"3.20","balance_usd":"5.60"}
Коды ошибок
HTTP
code
Когда
400
invalid_request
тело не JSON или не той формы, неизвестное поле, неверный параметр
401
unauthorized
нет ключа, ключ неверный или отозван
402
insufficient_funds
не хватает баланса; доп. поля need_usd (сколько не хватает), balance_usd
403
ip_not_allowed
запрос не с разрешённого IP-адреса
403
account_disabled
аккаунт отключён
404
not_found
нет такого товара (по id из каталога), номинала, заказа, записи журнала
409
price_changed
итог выше max_price_usd; доп. поле total_usd — текущий итог. Деньги не списаны
409
idempotency_conflict
этот Idempotency-Key уже использован для другого заказа или итог того заказа выше max_price_usd повтора
422
invalid_fields
поле покупателя не заполнено или неверно; доп. поле field
422
product_unavailable
товар или номинал сейчас не продаётся (у Steam, звёзд и Premium — и когда товара сейчас нет в каталоге)
422
out_of_stock
столько нет в наличии
422
quantity_out_of_range
количество или сумма вне пределов; доп. поля min, max
422
order_limit
сумма заказа больше потолка одного заказа
422
recipient_not_found
аккаунт Telegram не найден
422
already_premium
у получателя уже есть Telegram Premium
422
not_a_user
username принадлежит не пользователю (канал, бот)
429
rate_limited
лимит запросов; заголовок Retry-After
503
service_unavailable
покупки временно выключены, каталог загружается, выдача на паузе, получателя Telegram сейчас не проверить; Retry-After
500
internal
сбой у нас; покупку можно повторить с тем же Idempotency-Key
При ответах 4xx и 503 заказ не создаётся и деньги не списываются. При 500 повторите покупку с тем же Idempotency-Key: если заказ успел создаться, вернётся он — второго списания не будет.
Повторы и цена
Защита от двойной покупки
Связь иногда обрывается так, что запрос дошёл, а ответ — нет. Чтобы повтор не купил второй раз, передавайте в каждой покупке заголовок Idempotency-Key — например, номер заказа в вашей системе. Работает на всех покупках, включая Telegram.
Заголовок
Idempotency-Key: my-order-42
8–128 символов: латинские буквы, цифры, - и _. Один ключ — один заказ: для каждой новой покупки — новый ключ, для повтора той же покупки — прежний.
Повтор с тем же ключом и тем же телом — HTTP 200 и прежний заказ в текущем состоянии, без второго списания.
Тот же ключ с другим товаром, номиналом, количеством, суммой или значениями полей — 409 idempotency_conflict. Другой max_price_usd — тоже 409, если итог прежнего заказа выше нового потолка; иначе вернётся прежний заказ.
Без ключа каждый запрос — новый заказ.
Ключи общие для API и кабинета партнёра: ключ, уже использованный в кабинете, в API тоже считается занятым.
Потолок цены
Цены могут меняться. Передайте в теле покупки max_price_usd — например, цену, которую видел покупатель. Если итог заказа выше, заказ не создаётся и деньги не списываются: ответ 409 price_changed, в total_usd — текущий итог. Значение — строка или число, сравнивается с точностью до цента.
Ответ 409
{"ok":false,"error":"Цена изменилась: было $0.95, стало $0.97","code":"price_changed","total_usd":"0.97"}
Лимиты
Лимиты запросов
Что
Лимит
Все запросы с одним ключом
120 в минуту
Покупки
30 в минуту на партнёра
Проверка ID игрока — POST /topups/validate-id
10 в минуту на партнёра
Проверка получателя Telegram — POST /telegram/recipient
20 в минуту на партнёра
Поиск нового получателя Telegram — в проверках и покупках Stars и Premium, API и кабинет вместе
10 в минуту на партнёра
Сохранение и проверка вебхука — PUT /account/webhook и POST /account/webhook/test вместе
10 в минуту на партнёра
Превышение — 429 rate_limited и заголовок Retry-After: через сколько секунд повторить. С тем же заголовком приходит 503 service_unavailable — например, пока загружается каталог или выдача товара на паузе.
Лимиты в таблице, кроме поиска получателя Telegram, считаются для API отдельно от кабинета: покупки в кабинете их не расходуют. Кроме них есть общие лимиты сервиса на проверки ID игрока и получателя Telegram — одни на всех партнёров. Их превышение — тоже 429 с Retry-After, даже если ваши лимиты не исчерпаны.
Поиск получателя Telegram — ваша доля в общем лимите сервиса, один счётчик на API и кабинет: в него идут проверки получателя и покупки Stars и Premium. Получатель, которого недавно уже искали, берётся из памяти и лимит не тратит, поэтому проверка перед покупкой и сама покупка — один поиск. Пока доля исчерпана, проверка и покупка с новым получателем отклоняются с Retry-After, деньги не списываются.
Каталог не нужно запрашивать перед каждой покупкой: обновляйте его у себя по расписанию, а от изменения цены защитит потолок цены.
Заказ и статусы
Покупка создаёт заказ и сразу списывает деньги. Дальше заказ выполняется в фоне: следите за ним по GET /orders/{order_id} или по вебхукам. Номер заказа — вида ED-000123. В GET /orders есть и заказы, сделанные в кабинете.
Статусы
pendingprocessingcompleted
при сбоеrefunded
Статусы заказа
status
Что значит
pending
Принят, деньги списаны, выполнение ещё не началось.
processing
Выполняется.
completed
Выполнен. Коды карт и ключи игр — в delivery.codes, выдача текстом — в delivery.text.
failed
Не выполнен. Сейчас деньги возвращаются сразу, поэтому такой заказ вы увидите уже в статусе refunded.
refunded
Не выполнен, деньги вернулись на баланс. Причина — в failure_reason.
Окончательные статусы — completed и refunded: после них заказ больше не меняется.
Этапы Telegram
У заказов Telegram Stars и Premium есть поле telegram: этап выдачи stage и признак тестового заказа test (звёзды и Premium на самом деле не отправляются, деньги возвращаются на баланс). Итог по-прежнему — статус заказа.
Этапы выдачи Telegram
stage
Что значит
queued
В очереди.
preparing
Готовим отправку.
paying
Отправляем.
delivered
Выдано.
checking
Заказ проверяется вручную.
Объект заказа
Один и тот же объект — в ответах покупок, в GET /orders и в уведомлениях вебхука.
order
{"order_id":"ED-000123","status":"completed","kind":"topup","product_id":"topup:pubg_mobile_auto","item_id":"60_uc","product_name":"PUBG Mobile (Auto)","item_name":"60 UC","quantity":1,"amount":null,"currency":null,"fields":[{"key":"player_id","label":"Player ID","value":"5123456789"}],"price_usd":"0.97","total_usd":"0.97","delivery":{"codes":[],"text":null},"failure_reason":null,"telegram":null,"idempotency_key":"my-order-42","channel":"api","created_at":"2026-10-21T10:00:00.000Z","updated_at":"2026-10-21T10:00:05.000Z","completed_at":"2026-10-21T10:00:05.000Z"}
Поля объекта заказа
Поле
Тип
Описание
order_id
string
Номер заказа EasyDonat.
status
string
pending — принят, деньги списаны, выполнение ещё не началось; processing — выполняется; completed — выполнен; failed — не выполнен, деньги вот-вот вернутся (обычно неудача сразу становится возвратом, и этот статус вы не увидите); refunded — не выполнен или отменён, деньги вернулись на баланс. Окончательные статусы — completed и refunded; о неудаче сообщает order.refunded.
kind
string
topup — пополнение игр; giftcard — подарочная карта; gamekey — ключ игры; steam_topup — пополнение Steam; telegram — Telegram Stars и Premium; manual — услуга, купленная в кабинете (через API v1 не продаётся).
product_id
string
id товара: <kind>:<category_id или game_id>; у Steam — steam_topup:wallet, у Telegram — telegram:stars и telegram:premium.
item_id
string
Номинал: offer_id, card_id или key_id; у Steam — валюта (RUB…); у звёзд — stars; у Premium — срок в месяцах (3, 6, 12).
product_name
string
item_name
string
quantity
integer
Количество; у пополнений игр, Steam и Telegram — 1.Не меньше 1.
amount
number | null
У Steam — сумма в валюте currency; у звёзд Telegram — число звёзд; иначе null.
currency
string | null
У Steam — валюта кошелька; иначе null.Одно из: RUB, USD, UAH, KZT.
fields
object[]
Что указал покупатель.
fields[].key
string
fields[].label
string
fields[].value
string
Что указал покупатель; у секретных полей — ••••.
price_usd
string
Цена за единицу. Доллары США строкой с 2 знаками после точки.
total_usd
string
Итог — столько списано с баланса. Доллары США строкой с 2 знаками после точки.
delivery
object | null
Выдача — только у completed, иначе null. У пополнений игр и Steam товар зачисляется прямо на аккаунт, кодов нет.
delivery.codes
object[]
Коды карт и ключи игр; у остальных товаров — пустой список.
delivery.codes[].code
string
Код или ключ.
delivery.codes[].pin
string | null
PIN, если есть.
delivery.codes[].serial
string | null
Серийный номер, если есть.
delivery.codes[].note
string | null
Пояснение к коду, если есть.
delivery.text
string | null
Выдача текстом (например, у Telegram) или null.
failure_reason
string | null
Почему заказ не выполнен — по-русски; иначе null.
telegram
object | null
Ход выдачи Telegram Stars и Premium; у остальных — null.
Тестовый заказ: звёзды и Premium на самом деле не отправляются, деньги возвращаются на баланс.
idempotency_key
string | null
Idempotency-Key, с которым создан заказ; без ключа и у заказов из кабинета — null.
channel
string
Где создан заказ: через API или в кабинете.Одно из: api, cabinet.
created_at
string
Время в ISO 8601 UTC.
updated_at
string
Время в ISO 8601 UTC.
completed_at
string | null
Когда выполнен; иначе null.
Справочник
Все пути — от адреса https://easydonat.com/api/v1, ключ — в заголовке X-API-Key. В примерах ключ берётся из переменной окружения EASYDONAT_API_KEY, а Idempotency-Key у покупок — для образца: подставляйте свой, новый для каждой покупки. Ошибки 401, 403, 429, 500 и 503 возможны у любого вызова и описаны один раз — в «Ответы и ошибки».
{"ok":true,"order":{"order_id":"ED-000123","status":"completed","kind":"topup","product_id":"topup:pubg_mobile_auto","item_id":"60_uc","product_name":"PUBG Mobile (Auto)","item_name":"60 UC","quantity":1,"amount":null,"currency":null,"fields":[{"key":"player_id","label":"Player ID","value":"5123456789"}],"price_usd":"0.97","total_usd":"0.97","delivery":{"codes":[],"text":null},"failure_reason":null,"telegram":null,"idempotency_key":"my-order-42","channel":"api","created_at":"2026-10-21T10:00:00.000Z","updated_at":"2026-10-21T10:00:05.000Z","completed_at":"2026-10-21T10:00:05.000Z"}}
{"ok":true,"order":{"order_id":"ED-000126","status":"completed","kind":"telegram","product_id":"telegram:premium","item_id":"3","product_name":"Telegram Premium","item_name":"3 месяца","quantity":1,"amount":null,"currency":null,"fields":[{"key":"username","label":"Username в Telegram","value":"example_user"}],"price_usd":"12.66","total_usd":"12.66","delivery":{"codes":[],"text":"Telegram Premium на 3 месяца подарен @example_user"},"failure_reason":null,"telegram":{"stage":"delivered","test":false},"idempotency_key":"premium-3m-1","channel":"api","created_at":"2026-10-21T10:20:00.000Z","updated_at":"2026-10-21T10:21:30.000Z","completed_at":"2026-10-21T10:21:30.000Z"}}
Игры с пополнением по ID игрока. Список постраничный: пока meta.has_more — запрашивайте дальше с cursor = meta.next_cursor. Номиналы и поля покупателя — GET /topups/offers.
Параметры
Параметры вызова «Игры»
Параметр
Тип
Обязательный
Описание
limitв адресе
integer
нет
Записей на странице.От 1 до 500.По умолчанию 200.
cursorв адресе
string
нет
Продолжение списка — meta.next_cursor из прошлого ответа.
{"ok":true,"kind":"topup","items":[{"category_id":"pubg_mobile_auto","name":"PUBG Mobile (Auto)","region":"GLOBAL","note":"Region: Global\nAutomatic PUBG Mobile top-up.","imageurl":"https://easydonat.com/media/4e1f0c9a7b2d6e3f8a51.webp","can_validate":true}],"meta":{"next_cursor":"eyJpIjoyMDB9","has_more":true}}
Поля ответа
Поля ответа «Игры»
Поле
Тип
Описание
kind
string
Всегда topup.
items
object[]
items[].category_id
string
items[].name
string
items[].region
string | null
Регион товара, если известен: GLOBAL, BR, CIS, MY…
items[].note
string | null
Описание товара (часто на английском) или null.
items[].imageurl
string | null
Картинка товара (WebP 128×128) на easydonat.com или null.
items[].can_validate
boolean
Работает проверка ID игрока (POST /topups/validate-id).
meta
object
meta.next_cursor
string | null
Передайте в cursor за следующей страницей; null — это всё.
meta.has_more
boolean
Есть ли ещё страницы.
Коды ответа
Коды ответа «Игры»
HTTP
Что значит
200
Страница списка игр.
400
invalid_request — тело не JSON или не той формы, неизвестное поле, неверный параметр или заголовок Idempotency-Key.
Ещё возможны общие ошибки 401, 403, 429, 500, 503 — они одинаковы у всех вызовов, см. «Ответы и ошибки».
Примеры ответов с ошибкой
400 · example
{"ok":false,"error":"Некорректные данные заказа","code":"invalid_request"}
Номиналы и поля игры
GET/topups/offers
Номиналы игры с ценами и поля, которые нужно спросить у покупателя (ID игрока, зона, сервер…). Ключи полей — ключи объекта fields в покупке.
Параметры
Параметры вызова «Номиналы и поля игры»
Параметр
Тип
Обязательный
Описание
category_idв адресе
string
даобязательное
category_id игры из GET /topups.Пример: pubg_mobile_auto.
Ещё возможны общие ошибки 401, 403, 429, 500, 503 — они одинаковы у всех вызовов, см. «Ответы и ошибки».
Проверить ID игрока
POST/topups/validate-id
Проверка ID игрока до покупки — без списания денег. valid: null — проверить сейчас не удалось; это не значит, что ID неверный. Лимит — 10 проверок в минуту.
Тело запроса
Тело запроса «Проверить ID игрока»
Поле
Тип
Обязательное
Описание
category_id
string
даобязательное
category_id игры с can_validate: true.
fields
object
даобязательное
Поля покупателя: ключ — fields[].key из GET /topups/offers, значение — строка до 128 символов; у select — value одного из вариантов. Каждое поле обязательно, лишние ключи не учитываются.
{"ok":false,"error":"Для этой игры проверки ID нет","code":"product_unavailable"}
Купить пополнение
POST/topups/order
Покупка номинала игры. Деньги списываются сразу, заказ выполняется в фоне: следите за статусом через GET /orders/{order_id} или вебхук.
Параметры
Параметры вызова «Купить пополнение»
Параметр
Тип
Обязательный
Описание
Idempotency-Keyзаголовок
string
нет
Защита от двойной покупки (очень рекомендуем): 8–128 символов — латинские буквы, цифры, - и _; один ключ — один заказ. Повтор с тем же ключом и тем же телом — HTTP 200 и прежний заказ без второго списания; тот же ключ с другим телом — 409 idempotency_conflict. Тело сравнивается по смыслу: товар, номинал, количество, сумма и значения полей покупателя после той же обработки, что у покупки (пробелы по краям, @ и t.me/ у username); поля, которых у товара нет, не учитываются (их отбрасывает и первая покупка). max_price_usd повтора ниже итога прежнего заказа — тоже 409 idempotency_conflict; выше или равен (или его нет) — прежний заказ. Без ключа каждый запрос — новый заказ. Ключи общие для API и кабинета партнёра.Пример: my-order-42.
Тело запроса
Тело запроса «Купить пополнение»
Поле
Тип
Обязательное
Описание
category_id
string
даобязательное
category_id из GET /topups.
offer_id
string
даобязательное
offer_id из GET /topups/offers этой игры.
fields
object
даобязательное
Поля покупателя: ключ — fields[].key из GET /topups/offers, значение — строка до 128 символов; у select — value одного из вариантов. Каждое поле обязательно, лишние ключи не учитываются.
max_price_usd
string | number
нет
Потолок итога заказа в долларах — строка или число. Итог выше — 409 price_changed, деньги не списаны. Сравнивается с точностью до цента.
{"ok":true,"order":{"order_id":"ED-000123","status":"pending","kind":"topup","product_id":"topup:pubg_mobile_auto","item_id":"60_uc","product_name":"PUBG Mobile (Auto)","item_name":"60 UC","quantity":1,"amount":null,"currency":null,"fields":[{"key":"player_id","label":"Player ID","value":"5123456789"}],"price_usd":"0.97","total_usd":"0.97","delivery":null,"failure_reason":null,"telegram":null,"idempotency_key":"my-order-42","channel":"api","created_at":"2026-10-21T10:00:00.000Z","updated_at":"2026-10-21T10:00:00.000Z","completed_at":null}}
Повтор с тем же Idempotency-Key и тем же телом — прежний заказ в текущем состоянии, без второго списания.
201
Заказ создан, деньги списаны.
400
invalid_request — тело не JSON или не той формы, неизвестное поле, неверный параметр или заголовок Idempotency-Key.
402
insufficient_funds — не хватает баланса: need_usd — сколько не хватает, balance_usd — текущий баланс. Деньги не списаны.
404
not_found — нет такого товара (по id из каталога), номинала, заказа или записи журнала.
409
price_changed — итог выше max_price_usd, в total_usd — текущий итог; idempotency_conflict — этот Idempotency-Key уже использован для другого заказа (другие товар, номинал, количество, сумма или поля) или итог того заказа выше max_price_usd повтора. Деньги не списаны.
422
invalid_fields — поле покупателя не заполнено или неверно; product_unavailable — игра или номинал сейчас не продаётся. Деньги не списаны.
Ещё возможны общие ошибки 401, 403, 429, 500, 503 — они одинаковы у всех вызовов, см. «Ответы и ошибки».
Примеры ответов с ошибкой
{"ok":false,"error":"Некорректные данные заказа","code":"invalid_request"}
{"ok":false,"error":"Недостаточно средств на балансе","code":"insufficient_funds","need_usd":"3.20","balance_usd":"5.60"}
{"ok":false,"error":"Товар не найден или снят с продажи","code":"not_found"}
{"ok":false,"error":"Заказ не найден","code":"not_found"}
{"ok":false,"error":"Цена изменилась: было $0.95, стало $0.97","code":"price_changed","total_usd":"0.97"}
{"ok":false,"error":"Этот ключ запроса уже использован для другого заказа","code":"idempotency_conflict"}
{"ok":true,"kind":"giftcard","items":[{"category_id":"acash_my","name":"A-Cash","region":"MY","note":"Region: MY\nA-Cash Malaysia gift cards.\nCodes can be safely stored and redeemed later.","imageurl":null}],"meta":{"next_cursor":null,"has_more":false}}
Поля ответа
Поля ответа «Подарочные карты»
Поле
Тип
Описание
kind
string
Всегда giftcard.
items
object[]
items[].category_id
string
items[].name
string
items[].region
string | null
Регион товара, если известен: GLOBAL, BR, CIS, MY…
items[].note
string | null
Описание товара (часто на английском) или null.
items[].imageurl
string | null
Картинка товара (WebP 128×128) на easydonat.com или null.
meta
object
meta.next_cursor
string | null
Передайте в cursor за следующей страницей; null — это всё.
meta.has_more
boolean
Есть ли ещё страницы.
Коды ответа
Коды ответа «Подарочные карты»
HTTP
Что значит
200
Страница списка карт.
400
invalid_request — тело не JSON или не той формы, неизвестное поле, неверный параметр или заголовок Idempotency-Key.
Ещё возможны общие ошибки 401, 403, 429, 500, 503 — они одинаковы у всех вызовов, см. «Ответы и ошибки».
Примеры ответов с ошибкой
400 · example
{"ok":false,"error":"Некорректные данные заказа","code":"invalid_request"}
Номиналы карты
GET/giftcards/cards
Номиналы карты с ценами, остатком и пределами количества в одном заказе.
Параметры
Параметры вызова «Номиналы карты»
Параметр
Тип
Обязательный
Описание
category_idв адресе
string
даобязательное
category_id карты из GET /giftcards.Пример: acash_my.
{"ok":true,"category_id":"acash_my","name":"A-Cash","note":"Region: MY\nA-Cash Malaysia gift cards.\nCodes can be safely stored and redeemed later.","offers":[{"card_id":"10_myr","name":"10 MYR","price_usd":"2.86","stock":100,"min_order_quantity":1,"max_order_quantity":100,"available":true},{"card_id":"20_myr","name":"20 MYR","price_usd":"5.71","stock":100,"min_order_quantity":1,"max_order_quantity":100,"available":true}]}
Поля ответа
Поля ответа «Номиналы карты»
Поле
Тип
Описание
category_id
string
name
string
note
string | null
Описание товара (часто на английском) или null.
offers
object[]
offers[].card_id
string
offers[].name
string
offers[].price_usd
string
Доллары США строкой с 2 знаками после точки.
offers[].stock
integer | null
Остаток; null — не сообщается.
offers[].min_order_quantity
integer
Минимум штук в одном заказе.Не меньше 1.
offers[].max_order_quantity
integer
Максимум штук в одном заказе (не больше 100 и не больше остатка).Не меньше 1.
offers[].available
boolean
Можно ли купить сейчас.
Коды ответа
Коды ответа «Номиналы карты»
HTTP
Что значит
200
Номиналы.
400
invalid_request — тело не JSON или не той формы, неизвестное поле, неверный параметр или заголовок Idempotency-Key.
404
not_found — нет такого товара (по id из каталога), номинала, заказа или записи журнала.
Ещё возможны общие ошибки 401, 403, 429, 500, 503 — они одинаковы у всех вызовов, см. «Ответы и ошибки».
Примеры ответов с ошибкой
{"ok":false,"error":"Некорректные данные заказа","code":"invalid_request"}
{"ok":false,"error":"Товар не найден или снят с продажи","code":"not_found"}
{"ok":false,"error":"Заказ не найден","code":"not_found"}
Купить карты
POST/giftcards/order
Покупка одной или нескольких карт одного номинала. Деньги списываются сразу; коды — в order.delivery.codes, когда заказ completed.
Параметры
Параметры вызова «Купить карты»
Параметр
Тип
Обязательный
Описание
Idempotency-Keyзаголовок
string
нет
Защита от двойной покупки (очень рекомендуем): 8–128 символов — латинские буквы, цифры, - и _; один ключ — один заказ. Повтор с тем же ключом и тем же телом — HTTP 200 и прежний заказ без второго списания; тот же ключ с другим телом — 409 idempotency_conflict. Тело сравнивается по смыслу: товар, номинал, количество, сумма и значения полей покупателя после той же обработки, что у покупки (пробелы по краям, @ и t.me/ у username); поля, которых у товара нет, не учитываются (их отбрасывает и первая покупка). max_price_usd повтора ниже итога прежнего заказа — тоже 409 idempotency_conflict; выше или равен (или его нет) — прежний заказ. Без ключа каждый запрос — новый заказ. Ключи общие для API и кабинета партнёра.Пример: my-order-42.
Тело запроса
Тело запроса «Купить карты»
Поле
Тип
Обязательное
Описание
category_id
string
даобязательное
category_id из GET /giftcards.
card_id
string
даобязательное
card_id из GET /giftcards/cards этой карты.
quantity
integer
даобязательное
Сколько карт; в пределах min_order_quantity…max_order_quantity и остатка.От 1 до 100.
max_price_usd
string | number
нет
Потолок итога заказа в долларах — строка или число. Итог выше — 409 price_changed, деньги не списаны. Сравнивается с точностью до цента.
Повтор с тем же Idempotency-Key и тем же телом — прежний заказ в текущем состоянии, без второго списания.
201
Заказ создан, деньги списаны.
400
invalid_request — тело не JSON или не той формы, неизвестное поле, неверный параметр или заголовок Idempotency-Key.
402
insufficient_funds — не хватает баланса: need_usd — сколько не хватает, balance_usd — текущий баланс. Деньги не списаны.
404
not_found — нет такого товара (по id из каталога), номинала, заказа или записи журнала.
409
price_changed — итог выше max_price_usd, в total_usd — текущий итог; idempotency_conflict — этот Idempotency-Key уже использован для другого заказа (другие товар, номинал, количество, сумма или поля) или итог того заказа выше max_price_usd повтора. Деньги не списаны.
422
product_unavailable — карта или номинал сейчас не продаётся; out_of_stock — столько нет в наличии; quantity_out_of_range — количество вне пределов номинала (min, max). Деньги не списаны.
Ещё возможны общие ошибки 401, 403, 429, 500, 503 — они одинаковы у всех вызовов, см. «Ответы и ошибки».
Примеры ответов с ошибкой
{"ok":false,"error":"Некорректные данные заказа","code":"invalid_request"}
{"ok":false,"error":"Недостаточно средств на балансе","code":"insufficient_funds","need_usd":"3.20","balance_usd":"5.60"}
{"ok":false,"error":"Товар не найден или снят с продажи","code":"not_found"}
{"ok":false,"error":"Заказ не найден","code":"not_found"}
{"ok":false,"error":"Цена изменилась: было $0.95, стало $0.97","code":"price_changed","total_usd":"0.97"}
{"ok":false,"error":"Этот ключ запроса уже использован для другого заказа","code":"idempotency_conflict"}
{"ok":false,"error":"Этот номинал сейчас недоступен","code":"product_unavailable"}
{"ok":false,"error":"Столько нет в наличии: осталось 3","code":"out_of_stock"}
{"ok":false,"error":"Максимальное количество — 100","code":"quantity_out_of_range","min":1,"max":100}
Ключи игр
Ключи активации игр. Ключи приходят в order.delivery.codes, когда заказ выполнен.
Игры с ключами — в том числе те, чьи издания сейчас не продаются: можно ли купить издание, видно по available в GET /gamekeys/keys. Список постраничный: пока meta.has_more — запрашивайте дальше с cursor = meta.next_cursor. Издания и цены — GET /gamekeys/keys.
Параметры
Параметры вызова «Игры с ключами»
Параметр
Тип
Обязательный
Описание
limitв адресе
integer
нет
Записей на странице.От 1 до 500.По умолчанию 200.
cursorв адресе
string
нет
Продолжение списка — meta.next_cursor из прошлого ответа.
{"ok":true,"kind":"gamekey","items":[{"game_id":"7_days_to_die_global","name":"7 Days to Die","region":"GLOBAL","platform":"Steam","imageurl":"https://easydonat.com/media/9b0d2e7c4a1f6e3d5c88.webp"}],"meta":{"next_cursor":null,"has_more":false}}
Поля ответа
Поля ответа «Игры с ключами»
Поле
Тип
Описание
kind
string
Всегда gamekey.
items
object[]
items[].game_id
string
items[].name
string
items[].region
string | null
Регион товара, если известен: GLOBAL, BR, CIS, MY…
items[].platform
string | null
Где активируется ключ: Steam, Xbox, Rockstar…
items[].imageurl
string | null
Картинка товара (WebP 128×128) на easydonat.com или null.
meta
object
meta.next_cursor
string | null
Передайте в cursor за следующей страницей; null — это всё.
meta.has_more
boolean
Есть ли ещё страницы.
Коды ответа
Коды ответа «Игры с ключами»
HTTP
Что значит
200
Страница списка игр.
400
invalid_request — тело не JSON или не той формы, неизвестное поле, неверный параметр или заголовок Idempotency-Key.
Ещё возможны общие ошибки 401, 403, 429, 500, 503 — они одинаковы у всех вызовов, см. «Ответы и ошибки».
Примеры ответов с ошибкой
400 · example
{"ok":false,"error":"Некорректные данные заказа","code":"invalid_request"}
Ключи игры
GET/gamekeys/keys
Издания игры с ценами, остатком и пределами количества в одном заказе.
Параметры
Параметры вызова «Ключи игры»
Параметр
Тип
Обязательный
Описание
game_idв адресе
string
даобязательное
game_id игры из GET /gamekeys.Пример: 7_days_to_die_global.
{"ok":true,"game_id":"7_days_to_die_global","name":"7 Days to Die","region":"GLOBAL","platform":"Steam","keys":[{"key_id":"7_days_to_die","name":"7 Days to Die","price_usd":"18.07","stock":121,"min_order_quantity":1,"max_order_quantity":100,"available":true}]}
Поля ответа
Поля ответа «Ключи игры»
Поле
Тип
Описание
game_id
string
name
string
region
string | null
Регион товара, если известен: GLOBAL, BR, CIS, MY…
platform
string | null
keys
object[]
keys[].key_id
string
keys[].name
string
keys[].price_usd
string
Доллары США строкой с 2 знаками после точки.
keys[].stock
integer | null
Остаток; null — не сообщается.
keys[].min_order_quantity
integer
Минимум штук в одном заказе.Не меньше 1.
keys[].max_order_quantity
integer
Максимум штук в одном заказе (не больше 100 и не больше остатка).Не меньше 1.
keys[].available
boolean
Можно ли купить сейчас.
Коды ответа
Коды ответа «Ключи игры»
HTTP
Что значит
200
Ключи игры.
400
invalid_request — тело не JSON или не той формы, неизвестное поле, неверный параметр или заголовок Idempotency-Key.
404
not_found — нет такого товара (по id из каталога), номинала, заказа или записи журнала.
Ещё возможны общие ошибки 401, 403, 429, 500, 503 — они одинаковы у всех вызовов, см. «Ответы и ошибки».
Примеры ответов с ошибкой
{"ok":false,"error":"Некорректные данные заказа","code":"invalid_request"}
{"ok":false,"error":"Товар не найден или снят с продажи","code":"not_found"}
{"ok":false,"error":"Заказ не найден","code":"not_found"}
Купить ключи
POST/gamekeys/order
Покупка одного или нескольких ключей одного издания. Деньги списываются сразу; ключи — в order.delivery.codes, когда заказ completed.
Параметры
Параметры вызова «Купить ключи»
Параметр
Тип
Обязательный
Описание
Idempotency-Keyзаголовок
string
нет
Защита от двойной покупки (очень рекомендуем): 8–128 символов — латинские буквы, цифры, - и _; один ключ — один заказ. Повтор с тем же ключом и тем же телом — HTTP 200 и прежний заказ без второго списания; тот же ключ с другим телом — 409 idempotency_conflict. Тело сравнивается по смыслу: товар, номинал, количество, сумма и значения полей покупателя после той же обработки, что у покупки (пробелы по краям, @ и t.me/ у username); поля, которых у товара нет, не учитываются (их отбрасывает и первая покупка). max_price_usd повтора ниже итога прежнего заказа — тоже 409 idempotency_conflict; выше или равен (или его нет) — прежний заказ. Без ключа каждый запрос — новый заказ. Ключи общие для API и кабинета партнёра.Пример: my-order-42.
Тело запроса
Тело запроса «Купить ключи»
Поле
Тип
Обязательное
Описание
game_id
string
даобязательное
game_id из GET /gamekeys.
key_id
string
даобязательное
key_id из GET /gamekeys/keys этой игры.
quantity
integer
даобязательное
Сколько ключей; в пределах min_order_quantity…max_order_quantity и остатка.От 1 до 100.
max_price_usd
string | number
нет
Потолок итога заказа в долларах — строка или число. Итог выше — 409 price_changed, деньги не списаны. Сравнивается с точностью до цента.
{"ok":true,"order":{"order_id":"ED-000129","status":"pending","kind":"gamekey","product_id":"gamekey:7_days_to_die_global","item_id":"7_days_to_die","product_name":"7 Days to Die","item_name":"7 Days to Die","quantity":1,"amount":null,"currency":null,"fields":[],"price_usd":"18.07","total_usd":"18.07","delivery":null,"failure_reason":null,"telegram":null,"idempotency_key":"key-7dtd-1","channel":"api","created_at":"2026-10-21T11:10:00.000Z","updated_at":"2026-10-21T11:10:00.000Z","completed_at":null}}
Повтор с тем же Idempotency-Key и тем же телом — прежний заказ в текущем состоянии, без второго списания.
201
Заказ создан, деньги списаны.
400
invalid_request — тело не JSON или не той формы, неизвестное поле, неверный параметр или заголовок Idempotency-Key.
402
insufficient_funds — не хватает баланса: need_usd — сколько не хватает, balance_usd — текущий баланс. Деньги не списаны.
404
not_found — нет такого товара (по id из каталога), номинала, заказа или записи журнала.
409
price_changed — итог выше max_price_usd, в total_usd — текущий итог; idempotency_conflict — этот Idempotency-Key уже использован для другого заказа (другие товар, номинал, количество, сумма или поля) или итог того заказа выше max_price_usd повтора. Деньги не списаны.
422
product_unavailable — игра или издание сейчас не продаётся; out_of_stock — столько нет в наличии; quantity_out_of_range — количество вне пределов (min, max). Деньги не списаны.
Ещё возможны общие ошибки 401, 403, 429, 500, 503 — они одинаковы у всех вызовов, см. «Ответы и ошибки».
Примеры ответов с ошибкой
{"ok":false,"error":"Некорректные данные заказа","code":"invalid_request"}
{"ok":false,"error":"Недостаточно средств на балансе","code":"insufficient_funds","need_usd":"3.20","balance_usd":"5.60"}
{"ok":false,"error":"Товар не найден или снят с продажи","code":"not_found"}
{"ok":false,"error":"Заказ не найден","code":"not_found"}
{"ok":false,"error":"Цена изменилась: было $0.95, стало $0.97","code":"price_changed","total_usd":"0.97"}
{"ok":false,"error":"Этот ключ запроса уже использован для другого заказа","code":"idempotency_conflict"}
{"ok":false,"error":"Этот номинал сейчас недоступен","code":"product_unavailable"}
{"ok":false,"error":"Столько нет в наличии: осталось 3","code":"out_of_stock"}
{"ok":false,"error":"Максимальное количество — 100","code":"quantity_out_of_range","min":1,"max":100}
Пополнение Steam
Пополнение кошелька Steam по логину на любую сумму в RUB, USD, UAH или KZT.
Валюты кошелька Steam, цена 1 единицы валюты в долларах и пределы суммы. Итог заказа считает сервер: примерно amount × usd_per_unit, вверх до цента (не выше этой оценки).
Валюта кошелька Steam.Одно из: RUB, USD, UAH, KZT.
currencies[].usd_per_unit
string
Сколько долларов спишется за 1 единицу валюты. Цена единицы в долларах США строкой, до 9 знаков после точки.
currencies[].min_amount
number
Наименьшая сумма пополнения в этой валюте.
currencies[].max_amount
number
Наибольшая сумма пополнения в этой валюте.
Коды ответа
Коды ответа «Цены пополнения Steam»
HTTP
Что значит
200
Валюты и цены.
Ещё возможны общие ошибки 401, 403, 429, 500, 503 — они одинаковы у всех вызовов, см. «Ответы и ошибки».
Пополнить Steam
POST/steam-topup/order
Пополнение кошелька Steam по логину на сумму в выбранной валюте. Деньги списываются сразу, итог — в order.total_usd. Если Steam не примет логин, заказ не выполнится и деньги вернутся на баланс.
Параметры
Параметры вызова «Пополнить Steam»
Параметр
Тип
Обязательный
Описание
Idempotency-Keyзаголовок
string
нет
Защита от двойной покупки (очень рекомендуем): 8–128 символов — латинские буквы, цифры, - и _; один ключ — один заказ. Повтор с тем же ключом и тем же телом — HTTP 200 и прежний заказ без второго списания; тот же ключ с другим телом — 409 idempotency_conflict. Тело сравнивается по смыслу: товар, номинал, количество, сумма и значения полей покупателя после той же обработки, что у покупки (пробелы по краям, @ и t.me/ у username); поля, которых у товара нет, не учитываются (их отбрасывает и первая покупка). max_price_usd повтора ниже итога прежнего заказа — тоже 409 idempotency_conflict; выше или равен (или его нет) — прежний заказ. Без ключа каждый запрос — новый заказ. Ключи общие для API и кабинета партнёра.Пример: my-order-42.
Тело запроса
Тело запроса «Пополнить Steam»
Поле
Тип
Обязательное
Описание
steamLogin
string
даобязательное
Логин аккаунта Steam (не имя профиля), до 128 символов.
currency
string
даобязательное
Валюта кошелька Steam.Одно из: RUB, USD, UAH, KZT.
amount
number
даобязательное
Сумма пополнения в валюте currency, не больше 2 знаков после точки; в пределах min_amount…max_amount из GET /steam-topup/rates.Больше 0.
max_price_usd
string | number
нет
Потолок итога заказа в долларах — строка или число. Итог выше — 409 price_changed, деньги не списаны. Сравнивается с точностью до цента.
Повтор с тем же Idempotency-Key и тем же телом — прежний заказ в текущем состоянии, без второго списания.
201
Заказ создан, деньги списаны.
400
invalid_request — тело не JSON или не той формы, неизвестное поле, неверный параметр или заголовок Idempotency-Key.
402
insufficient_funds — не хватает баланса: need_usd — сколько не хватает, balance_usd — текущий баланс. Деньги не списаны.
409
price_changed — итог выше max_price_usd, в total_usd — текущий итог; idempotency_conflict — этот Idempotency-Key уже использован для другого заказа (другие товар, номинал, количество, сумма или поля) или итог того заказа выше max_price_usd повтора. Деньги не списаны.
422
invalid_fields — логин не указан или неверен (field: steamLogin); quantity_out_of_range — сумма вне пределов валюты (min, max); product_unavailable — пополнение в этой валюте или пополнение Steam вообще сейчас недоступно (тогда и GET /steam-topup/rates отдаёт пустой currencies). Деньги не списаны.
Ещё возможны общие ошибки 401, 403, 429, 500, 503 — они одинаковы у всех вызовов, см. «Ответы и ошибки».
Примеры ответов с ошибкой
{"ok":false,"error":"Некорректные данные заказа","code":"invalid_request"}
{"ok":false,"error":"Недостаточно средств на балансе","code":"insufficient_funds","need_usd":"3.20","balance_usd":"5.60"}
{"ok":false,"error":"Цена изменилась: было $0.95, стало $0.97","code":"price_changed","total_usd":"0.97"}
{"ok":false,"error":"Этот ключ запроса уже использован для другого заказа","code":"idempotency_conflict"}
{"ok":false,"error":"Пополнение в этой валюте сейчас недоступно","code":"product_unavailable"}
Telegram
Telegram Stars и Telegram Premium по username. Получатель проверяется до списания денег: не найден — 422, деньги не списаны; заказ создаётся в статусе pending. Поисков получателя у Telegram (мимо кеша) — не больше 10 в минуту на партнёра, вместе с кабинетом и покупками; сверх — проверка отвечает 429, покупка — 503 service_unavailable с Retry-After, деньги не списаны.
Срок Telegram Premium в месяцах.Одно из: 3, 6, 12.
plans[].price_usd
string
Доллары США строкой с 2 знаками после точки.
plans[].available
boolean
Можно ли купить сейчас.
Коды ответа
Коды ответа «Цены Premium»
HTTP
Что значит
200
Сроки и цены.
Ещё возможны общие ошибки 401, 403, 429, 500, 503 — они одинаковы у всех вызовов, см. «Ответы и ошибки».
Проверить получателя
POST/telegram/recipient
Проверка получателя до покупки — без списания денег: есть ли такой аккаунт и может ли он получить звёзды или Premium. found: null — проверить сейчас нельзя (в том числе пока Telegram выдаётся вручную). Лимит — 20 проверок в минуту; из них поисков у Telegram мимо кеша — не больше 10 в минуту на партнёра, вместе с кабинетом и покупками (сверх — 429).
Тело запроса
Тело запроса «Проверить получателя»
Поле
Тип
Обязательное
Описание
telegram_username
string
даобязательное
Username получателя: с @ или без, можно ссылкой t.me/…; 5–32 символа — латинские буквы, цифры и _.
product
string
даобязательное
Что собираетесь купить.Одно из: stars, premium.
months
integer
нет
Только для premium: срок. Срок Telegram Premium в месяцах.Одно из: 3, 6, 12.
{"ok":true,"found":false,"name":null,"message":"Аккаунт @example_user в Telegram не найден"}
{"ok":true,"found":false,"name":null,"message":"У @example_user уже есть Telegram Premium — подарить нельзя"}
{"ok":true,"found":null,"name":null,"message":"Проверка сейчас недоступна — попробуйте через минуту"}
Поля ответа
Поля ответа «Проверить получателя»
Поле
Тип
Описание
found
boolean | null
true — можно покупать; false — нельзя (аккаунт не найден, уже есть Premium, это канал или бот — см. message); null — проверить сейчас нельзя.
name
string | null
Имя получателя в Telegram, если найден.
message
string | null
Пояснение по-русски, если купить нельзя или проверка не удалась.
Коды ответа
Коды ответа «Проверить получателя»
HTTP
Что значит
200
Итог проверки.
400
invalid_request — тело не JSON или не той формы, неизвестное поле, неверный параметр или заголовок Idempotency-Key.
422
invalid_fields — username неверного вида (field: telegram_username); product_unavailable — товар сейчас не продаётся (звёзды сняты с продажи, срок Premium недоступен).
Ещё возможны общие ошибки 401, 403, 429, 500, 503 — они одинаковы у всех вызовов, см. «Ответы и ошибки».
Примеры ответов с ошибкой
{"ok":false,"error":"Некорректные данные заказа","code":"invalid_request"}
{"ok":false,"error":"«Username в Telegram»: от 5 до 32 символов — латинские буквы, цифры и _","code":"invalid_fields","field":"telegram_username"}
{"ok":false,"error":"Звёзды сейчас не продаются","code":"product_unavailable"}
Купить звёзды
POST/telegram/stars/buy
Покупка Telegram Stars для username. Получатель проверяется до списания денег. Выдача идёт в фоне: этап — в order.telegram.stage, итог — статус заказа.
Параметры
Параметры вызова «Купить звёзды»
Параметр
Тип
Обязательный
Описание
Idempotency-Keyзаголовок
string
нет
Защита от двойной покупки (очень рекомендуем): 8–128 символов — латинские буквы, цифры, - и _; один ключ — один заказ. Повтор с тем же ключом и тем же телом — HTTP 200 и прежний заказ без второго списания; тот же ключ с другим телом — 409 idempotency_conflict. Тело сравнивается по смыслу: товар, номинал, количество, сумма и значения полей покупателя после той же обработки, что у покупки (пробелы по краям, @ и t.me/ у username); поля, которых у товара нет, не учитываются (их отбрасывает и первая покупка). max_price_usd повтора ниже итога прежнего заказа — тоже 409 idempotency_conflict; выше или равен (или его нет) — прежний заказ. Без ключа каждый запрос — новый заказ. Ключи общие для API и кабинета партнёра.Пример: my-order-42.
Тело запроса
Тело запроса «Купить звёзды»
Поле
Тип
Обязательное
Описание
telegram_username
string
даобязательное
Username получателя: с @ или без, можно ссылкой t.me/…; 5–32 символа — латинские буквы, цифры и _.
quantity
integer
даобязательное
Число звёзд; в пределах min_amount…max_amount из GET /telegram/stars.Не меньше 50.
max_price_usd
string | number
нет
Потолок итога заказа в долларах — строка или число. Итог выше — 409 price_changed, деньги не списаны. Сравнивается с точностью до цента.
{"ok":true,"order":{"order_id":"ED-000125","status":"pending","kind":"telegram","product_id":"telegram:stars","item_id":"stars","product_name":"Telegram Stars","item_name":"500 звёзд","quantity":1,"amount":500,"currency":null,"fields":[{"key":"username","label":"Username в Telegram","value":"example_user"}],"price_usd":"8.80","total_usd":"8.80","delivery":null,"failure_reason":null,"telegram":{"stage":"queued","test":false},"idempotency_key":"stars-500-1","channel":"api","created_at":"2026-10-21T10:10:00.000Z","updated_at":"2026-10-21T10:10:00.000Z","completed_at":null}}
Повтор с тем же Idempotency-Key и тем же телом — прежний заказ в текущем состоянии, без второго списания.
201
Заказ создан, деньги списаны.
400
invalid_request — тело не JSON или не той формы, неизвестное поле, неверный параметр или заголовок Idempotency-Key.
402
insufficient_funds — не хватает баланса: need_usd — сколько не хватает, balance_usd — текущий баланс. Деньги не списаны.
409
price_changed — итог выше max_price_usd, в total_usd — текущий итог; idempotency_conflict — этот Idempotency-Key уже использован для другого заказа (другие товар, номинал, количество, сумма или поля) или итог того заказа выше max_price_usd повтора. Деньги не списаны.
422
invalid_fields — username неверного вида (field: telegram_username); quantity_out_of_range — число звёзд вне пределов (min, max — те же, что min_amount и max_amount в GET /telegram/stars: максимум уже с потолком суммы одного заказа); order_limit — даже минимум звёзд дороже потолка одного заказа; recipient_not_found — аккаунт не найден; not_a_user — username принадлежит каналу или боту; product_unavailable — звёзды сейчас не продаются. Деньги не списаны.
Ещё возможны общие ошибки 401, 403, 429, 500, 503 — они одинаковы у всех вызовов, см. «Ответы и ошибки».
Примеры ответов с ошибкой
{"ok":false,"error":"Некорректные данные заказа","code":"invalid_request"}
{"ok":false,"error":"Недостаточно средств на балансе","code":"insufficient_funds","need_usd":"3.20","balance_usd":"5.60"}
{"ok":false,"error":"Цена изменилась: было $0.95, стало $0.97","code":"price_changed","total_usd":"0.97"}
{"ok":false,"error":"Этот ключ запроса уже использован для другого заказа","code":"idempotency_conflict"}
{"ok":false,"error":"«Username в Telegram»: от 5 до 32 символов — латинские буквы, цифры и _","code":"invalid_fields","field":"telegram_username"}
{"ok":false,"error":"Не больше $200.00 за один заказ","code":"order_limit"}
{"ok":false,"error":"Аккаунт @example_user в Telegram не найден","code":"recipient_not_found"}
{"ok":false,"error":"Этот username принадлежит каналу или боту","code":"not_a_user"}
{"ok":false,"error":"Звёзды сейчас не продаются","code":"product_unavailable"}
Подарить Premium
POST/telegram/premium/buy
Подарок Telegram Premium на 3, 6 или 12 месяцев для username. Получатель проверяется до списания денег. Выдача идёт в фоне: этап — в order.telegram.stage, итог — статус заказа.
Параметры
Параметры вызова «Подарить Premium»
Параметр
Тип
Обязательный
Описание
Idempotency-Keyзаголовок
string
нет
Защита от двойной покупки (очень рекомендуем): 8–128 символов — латинские буквы, цифры, - и _; один ключ — один заказ. Повтор с тем же ключом и тем же телом — HTTP 200 и прежний заказ без второго списания; тот же ключ с другим телом — 409 idempotency_conflict. Тело сравнивается по смыслу: товар, номинал, количество, сумма и значения полей покупателя после той же обработки, что у покупки (пробелы по краям, @ и t.me/ у username); поля, которых у товара нет, не учитываются (их отбрасывает и первая покупка). max_price_usd повтора ниже итога прежнего заказа — тоже 409 idempotency_conflict; выше или равен (или его нет) — прежний заказ. Без ключа каждый запрос — новый заказ. Ключи общие для API и кабинета партнёра.Пример: my-order-42.
Тело запроса
Тело запроса «Подарить Premium»
Поле
Тип
Обязательное
Описание
telegram_username
string
даобязательное
Username получателя: с @ или без, можно ссылкой t.me/…; 5–32 символа — латинские буквы, цифры и _.
months
integer
даобязательное
Срок Telegram Premium в месяцах.Одно из: 3, 6, 12.
max_price_usd
string | number
нет
Потолок итога заказа в долларах — строка или число. Итог выше — 409 price_changed, деньги не списаны. Сравнивается с точностью до цента.
{"ok":true,"order":{"order_id":"ED-000126","status":"pending","kind":"telegram","product_id":"telegram:premium","item_id":"3","product_name":"Telegram Premium","item_name":"3 месяца","quantity":1,"amount":null,"currency":null,"fields":[{"key":"username","label":"Username в Telegram","value":"example_user"}],"price_usd":"12.66","total_usd":"12.66","delivery":null,"failure_reason":null,"telegram":{"stage":"queued","test":false},"idempotency_key":"premium-3m-1","channel":"api","created_at":"2026-10-21T10:20:00.000Z","updated_at":"2026-10-21T10:20:00.000Z","completed_at":null}}
Повтор с тем же Idempotency-Key и тем же телом — прежний заказ в текущем состоянии, без второго списания.
201
Заказ создан, деньги списаны.
400
invalid_request — тело не JSON или не той формы, неизвестное поле, неверный параметр или заголовок Idempotency-Key.
402
insufficient_funds — не хватает баланса: need_usd — сколько не хватает, balance_usd — текущий баланс. Деньги не списаны.
409
price_changed — итог выше max_price_usd, в total_usd — текущий итог; idempotency_conflict — этот Idempotency-Key уже использован для другого заказа (другие товар, номинал, количество, сумма или поля) или итог того заказа выше max_price_usd повтора. Деньги не списаны.
422
invalid_fields — username неверного вида (field: telegram_username); recipient_not_found — аккаунт не найден; already_premium — у получателя уже есть Premium; not_a_user — username принадлежит каналу или боту; order_limit — сумма больше потолка одного заказа; product_unavailable — этот срок сейчас не продаётся. Деньги не списаны.
Ещё возможны общие ошибки 401, 403, 429, 500, 503 — они одинаковы у всех вызовов, см. «Ответы и ошибки».
Примеры ответов с ошибкой
{"ok":false,"error":"Некорректные данные заказа","code":"invalid_request"}
{"ok":false,"error":"Недостаточно средств на балансе","code":"insufficient_funds","need_usd":"3.20","balance_usd":"5.60"}
{"ok":false,"error":"Цена изменилась: было $0.95, стало $0.97","code":"price_changed","total_usd":"0.97"}
{"ok":false,"error":"Этот ключ запроса уже использован для другого заказа","code":"idempotency_conflict"}
{"ok":false,"error":"«Username в Telegram»: от 5 до 32 символов — латинские буквы, цифры и _","code":"invalid_fields","field":"telegram_username"}
{"ok":false,"error":"Аккаунт @example_user в Telegram не найден","code":"recipient_not_found"}
{"ok":false,"error":"У @example_user уже есть Telegram Premium — подарить нельзя","code":"already_premium"}
{"ok":false,"error":"Этот username принадлежит каналу или боту","code":"not_a_user"}
{"ok":false,"error":"Не больше $200.00 за один заказ","code":"order_limit"}
{"ok":false,"error":"Этот номинал сейчас недоступен","code":"product_unavailable"}
Вебхук
Уведомления о смене статуса заказов на ваш адрес (PUT /account/webhook): order.processing, order.completed и order.refunded (так приходит и неудача: заказ не выполнен, деньги вернулись), а также webhook.test. Подпись, повторы и пример проверки — в разделе «Вебхуки».
Секрет подписи уведомлений. Храните его так же бережно, как ключ API.
webhook.consecutive_failures
integer
Неудачных попыток подряд: считается каждая попытка, в том числе повторы одного события и проверки webhook.test; удачная попытка обнуляет счётчик, смена адреса — тоже.
webhook.last_success_at
string | null
webhook.last_failure_at
string | null
webhook.last_failure_reason
string | null
Почему не удалась последняя неудачная попытка.
webhook.created_at
string
Время в ISO 8601 UTC.
webhook.updated_at
string
Время в ISO 8601 UTC.
Коды ответа
Коды ответа «Настройки вебхука»
HTTP
Что значит
200
Вебхук или null.
Ещё возможны общие ошибки 401, 403, 429, 500, 503 — они одинаковы у всех вызовов, см. «Ответы и ошибки».
Сохранить вебхук
PUT/account/webhook
Создать вебхук или сменить адрес и включение. При первом сохранении создаётся секрет подписи whsec_…. Уведомления приходят о каждой следующей смене статуса — в том числе у заказов, созданных раньше и ещё не завершённых; о прошлых сменах уведомлений не будет. Без enabled: у нового вебхука — включён, у существующего — прежнее значение. Адрес проверяется (в том числе по DNS) при каждом сохранении.
Тело запроса
Тело запроса «Сохранить вебхук»
Поле
Тип
Обязательное
Описание
url
string
даобязательное
Только https://, порт 443 (по умолчанию) или 8443, без логина и пароля в адресе, не длиннее 2048 символов (после кодирования: не-ASCII в пути считается как %XX), не на частные, локальные и служебные адреса.
Секрет подписи уведомлений. Храните его так же бережно, как ключ API.
webhook.consecutive_failures
integer
Неудачных попыток подряд: считается каждая попытка, в том числе повторы одного события и проверки webhook.test; удачная попытка обнуляет счётчик, смена адреса — тоже.
webhook.last_success_at
string | null
webhook.last_failure_at
string | null
webhook.last_failure_reason
string | null
Почему не удалась последняя неудачная попытка.
webhook.created_at
string
Время в ISO 8601 UTC.
webhook.updated_at
string
Время в ISO 8601 UTC.
Коды ответа
Коды ответа «Сохранить вебхук»
HTTP
Что значит
200
Вебхук сохранён.
400
invalid_request — тело не той формы или адрес не подходит (не https://, порт не 443 и не 8443, логин и пароль в адресе, длиннее 2048 символов — считая уже закодированные символы не латиницей, частный или локальный адрес).
Ещё возможны общие ошибки 401, 403, 429, 500, 503 — они одинаковы у всех вызовов, см. «Ответы и ошибки».
Примеры ответов с ошибкой
400 · example
{"ok":false,"error":"Адрес вебхука должен начинаться с https://","code":"invalid_request"}
Удалить вебхук
DELETE/account/webhook
Удаляет вебхук и останавливает отправку уведомлений.
Ещё возможны общие ошибки 401, 403, 429, 500, 503 — они одинаковы у всех вызовов, см. «Ответы и ошибки».
Новый секрет
POST/account/webhook/secret/regenerate
Создаёт новый секрет подписи. Старый перестаёт действовать сразу — обновите его у себя: уведомления, которые ещё не ушли, будут подписаны новым. Тело запроса не нужно: пустое тело принимается при любом Content-Type, {} — тоже; другое тело — 400.
Пример запроса
curl
curl-X POST "https://easydonat.com/api/v1/account/webhook/secret/regenerate" \
-H"X-API-Key: $EASYDONAT_API_KEY"
invalid_request — тело не пустое и не {}: у этого вызова параметров нет. Не JSON — «Ожидается JSON (Content-Type: application/json)», JSON с полями (или не объект) — «Тело запроса не нужно: пустое или {}».
404
not_found — вебхук не настроен.
Ещё возможны общие ошибки 401, 403, 429, 500, 503 — они одинаковы у всех вызовов, см. «Ответы и ошибки».
Примеры ответов с ошибкой
{"ok":false,"error":"Тело запроса не нужно: пустое или {}","code":"invalid_request"}
{"ok":false,"error":"Вебхук не настроен","code":"not_found"}
Проверить вебхук
POST/account/webhook/test
Одна отправка события webhook.test на ваш адрес, без повторов. Итог отправки — всегда HTTP 200: ok — ответил ли ваш адрес кодом 2xx. Тело запроса не нужно: пустое тело принимается при любом Content-Type, {} — тоже; другое тело — 400.
Пример запроса
curl
curl-X POST "https://easydonat.com/api/v1/account/webhook/test" \
-H"X-API-Key: $EASYDONAT_API_KEY"
Ответ 200
{"ok":true,"status":200,"error":null}
{"ok":false,"status":null,"error":"Нет ответа за 10 секунд"}
Поля ответа
Поля ответа «Проверить вебхук»
Поле
Тип
Описание
ok
boolean
Ответил ли ваш адрес кодом 2xx.
status
integer | null
HTTP-статус ответа; null — ответа не было.
error
string | null
Что пошло не так.
Коды ответа
Коды ответа «Проверить вебхук»
HTTP
Что значит
200
Итог отправки.
400
invalid_request — тело не пустое и не {}: у этого вызова параметров нет. Не JSON — «Ожидается JSON (Content-Type: application/json)», JSON с полями (или не объект) — «Тело запроса не нужно: пустое или {}».
404
not_found — вебхук не настроен.
Ещё возможны общие ошибки 401, 403, 429, 500, 503 — они одинаковы у всех вызовов, см. «Ответы и ошибки».
Примеры ответов с ошибкой
{"ok":false,"error":"Тело запроса не нужно: пустое или {}","code":"invalid_request"}
{"ok":false,"error":"Вебхук не настроен","code":"not_found"}
Последние 50 доставок, новые сверху.До 50 элементов.
items[].id
integer
id доставки — как в заголовке X-EasyDonat-Delivery.Не меньше 1.
items[].event
string
order.processing, order.completed, order.refunded — заказ перешёл в этот статус (order.refunded — заказ не выполнен и деньги вернулись: отдельного события о неудаче нет); webhook.test — проверка вебхука.
items[].event_id
string
id события — как id в теле уведомления.
items[].order_id
string | null
Заказ события; у webhook.test — null.
items[].attempt
integer
Номер попытки, 1–9. Если ваш адрес не ответил вовсе (DNS, соединение, таймаут), попытка засчитывается и остальным доставкам, которые ждали отправки, — без отправки (в error так и написано): срок 9 попыток идёт от события.От 1 до 9.
items[].status
string
pending — ждёт отправки или повтора; delivered — ваш адрес ответил 2xx; failed — все попытки неудачны, либо доставка отменена (по заказу поставлено событие о новом статусе, вебхук удалён) или проверка прервалась — причина в error.
items[].response_status
integer | null
HTTP-статус ответа; null — ответа не было.
items[].duration_ms
integer | null
Сколько длилась отправка, мс.
items[].error
string | null
Что пошло не так.
items[].created_at
string
Время в ISO 8601 UTC.
items[].next_retry_at
string | null
Когда следующий повтор; null — повторов больше не будет.
Коды ответа
Коды ответа «Журнал доставок»
HTTP
Что значит
200
Доставки.
Ещё возможны общие ошибки 401, 403, 429, 500, 503 — они одинаковы у всех вызовов, см. «Ответы и ошибки».
Вебхуки
Вебхук — уведомление на ваш адрес, когда меняется статус заказа: созданного через API или в кабинете. С ним не нужно постоянно опрашивать GET /orders.
Как подключить
Сохраните адрес в кабинете («Интеграция») или вызовом PUT /account/webhook. При первом сохранении создаётся секрет подписи whsec_… — он виден в кабинете и в GET /account/webhook.
После того как вебхук создан, уведомления приходят о каждой следующей смене статуса — в том числе у заказов, созданных раньше и ещё не завершённых. О сменах статуса до создания вебхука уведомлений не будет.
Адрес — только https:// на порту 443 (по умолчанию) или 8443, без логина и пароля, не длиннее 2048 символов (символы не латиницей в пути считаются в закодированном виде %XX), не на частные, локальные и служебные адреса: это проверяется при сохранении и при каждой отправке. Редиректы не выполняются.
События
События вебхука
type
Когда приходит
order.processing
Заказ начал выполняться. Приходит при смене статуса любого заказа партнёра — из API и из кабинета. Подпись, повторы и сроки ответа — в описании раздела «Вебхук».
order.completed
Заказ выполнен; коды карт и ключи — в data.order.delivery.
order.refunded
Деньги за заказ вернулись на баланс партнёра: заказ не выполнен или отменён, причина — в data.order.failure_reason. Это и есть уведомление о неудаче: отдельного события о статусе failed нет — неудача сразу становится возвратом.
webhook.test
Отправляется по POST /account/webhook/test один раз, без повторов. Подписана так же, как остальные события.
Отдельного события о сбое нет: если заказ не выполнился, деньги сразу возвращаются на баланс и приходит order.refunded, причина — в data.order.failure_reason.
Запрос
POST на ваш адрес с телом в JSON (Content-Type: application/json) и заголовками:
Заголовки уведомления
Заголовок
Что в нём
X-EasyDonat-Event
Тип события — то же, что type в теле.
X-EasyDonat-Delivery
id доставки — как id в журнале доставок (GET /account/webhook/deliveries). У всех попыток одного события он один и тот же, номер попытки — attempt в журнале.
X-EasyDonat-Signature
t=<unix-время>,v1=<hex> — подпись: HMAC-SHA256 с секретом вебхука от строки <t>.<сырое тело запроса>. Как проверять — ниже.
Поля тела уведомления
Поле
Тип
Описание
id
string
id события. Одно событие может прийти больше одного раза — отсеивайте повторы по нему, а сами смены статуса обрабатывайте идемпотентно по паре (order_id, status).
type
string
order.processing, order.completed, order.refunded — заказ перешёл в этот статус (order.refunded — заказ не выполнен и деньги вернулись: отдельного события о неудаче нет); webhook.test — проверка вебхука.
created_at
string
Время в ISO 8601 UTC.
data
object
У событий order.* — заказ на момент отправки: в повторе type остаётся исходным, а data.order.status — текущий (заказ мог успеть выполниться). Статус берите из data.order.status и обрабатывайте идемпотентно по паре (order_id, status): два события с разными id могут нести один и тот же статус — второе пропускайте. Как только по заказу поставлено событие о новом статусе, прежнее недоставленное отменяется. У webhook.test поля order может не быть.
{"id":"evt_3b1f6c2a9d8e4f70","type":"order.completed","created_at":"2026-10-21T10:00:05.000Z","data":{"order":{"order_id":"ED-000123","status":"completed","kind":"topup","product_id":"topup:pubg_mobile_auto","item_id":"60_uc","product_name":"PUBG Mobile (Auto)","item_name":"60 UC","quantity":1,"amount":null,"currency":null,"fields":[{"key":"player_id","label":"Player ID","value":"5123456789"}],"price_usd":"0.97","total_usd":"0.97","delivery":{"codes":[],"text":null},"failure_reason":null,"telegram":null,"idempotency_key":"my-order-42","channel":"api","created_at":"2026-10-21T10:00:00.000Z","updated_at":"2026-10-21T10:00:05.000Z","completed_at":"2026-10-21T10:00:05.000Z"}}}
{"id":"evt_e2a9b4c8d1f07356","type":"order.refunded","created_at":"2026-10-21T10:05:41.000Z","data":{"order":{"order_id":"ED-000124","status":"refunded","kind":"steam_topup","product_id":"steam_topup:wallet","item_id":"RUB","product_name":"Steam","item_name":"500 RUB","quantity":1,"amount":500,"currency":"RUB","fields":[{"key":"steamLogin","label":"Логин Steam","value":"example_login"}],"price_usd":"6.58","total_usd":"6.58","delivery":null,"failure_reason":"Steam не принял данные: проверьте логин и сумму","telegram":null,"idempotency_key":"steam-500-rub-1","channel":"api","created_at":"2026-10-21T10:05:00.000Z","updated_at":"2026-10-21T10:05:41.000Z","completed_at":null}}}
Подпись доказывает, что уведомление отправили мы и его не изменили по дороге. Проверяйте её у каждого запроса:
Возьмите сырое тело запроса — байты как пришли, до разбора JSON.
Из заголовка X-EasyDonat-Signature: t=…,v1=… достаньте t (время отправки, секунды Unix) и v1.
Посчитайте HMAC-SHA256 от строки <t>.<сырое тело>. Ключ — секрет вебхука целиком, вместе с whsec_; результат — 64 шестнадцатеричных символа.
Сравните результат с v1 сравнением за постоянное время (timingSafeEqual, hash_equals, hmac.compare_digest), а не обычным ==.
Отклоните запрос, если t отличается от текущего времени больше чем на 5 минут: так перехваченный запрос не повторить позже.
// Node.js 18+ и Express. Сохраните как .mjs или добавьте "type": "module" в package.json — код на import.import crypto from"node:crypto";
import express from"express";
const TOLERANCE_SEC = 5 * 60; // запросы старше 5 минут отклоняемconst SECRET_RE = /^whsec_[0-9a-f]{32}$/; // секрет вебхука: «whsec_» и 32 символа 0–9, a–f/** rawBody — тело запроса как есть (Buffer), до JSON.parse. */function verifySignature(rawBody, header, secret) {
// Пустой или искажённый секрет — ошибка настройки, а не чужая подпись: HMAC с пустым ключом посчитает кто угодно.if (typeof secret !== "string" || !SECRET_RE.test(secret)) {
thrownew Error("Секрет вебхука не задан или не похож на whsec_…");
}
const parts = {};
for (const item of String(header ?? "").split(",")) {
const i = item.indexOf("=");
if (i > 0) parts[item.slice(0, i).trim()] = item.slice(i + 1).trim();
}
const t = Number(parts.t);
if (!Number.isInteger(t) || !/^[0-9a-f]{64}$/.test(parts.v1 ?? "")) returnfalse;
if (Math.abs(Date.now() / 1000 - t) > TOLERANCE_SEC) returnfalse;
// Ключ HMAC — секрет целиком, вместе с «whsec_».const expected = crypto.createHmac("sha256", secret).update(`${t}.`).update(rawBody).digest();
return crypto.timingSafeEqual(expected, Buffer.from(parts.v1, "hex"));
}
const SECRET = process.env.EASYDONAT_WEBHOOK_SECRET; // whsec_…const app = express();
asyncfunction handleEvent(event) {
// Ваша обработка: event.type — что случилось, event.data.order — заказ на момент отправки.
}
// express.raw — тело байт в байт: после разбора и обратной сборки JSON подпись не сойдётся.
app.post("/hooks/easydonat", express.raw({ type: "application/json" }), (req, res) => {
// Секрет не задан — verifySignature бросит исключение, и Express ответит 500.if (!verifySignature(req.body, req.get("X-EasyDonat-Signature"), SECRET)) {
return res.status(400).send("bad signature");
}
const event = JSON.parse(req.body.toString("utf8"));
res.sendStatus(200); // ответить нужно за 10 секунд — заказ обрабатываем уже после ответа// Одно событие может прийти дважды: пропускайте уже обработанные event.id.// Источник правды — GET /orders/{order_id}.
handleEvent(event).catch(console.error);
});
app.listen(3000);
<?php
// PHP 7.4+const TOLERANCE_SEC = 300; // запросы старше 5 минут отклоняем// Секрет вебхука: «whsec_» и 32 символа 0–9, a–f. \z, а не $: в PHP $ пропускает перевод строки в конце.const SECRET_PATTERN = '/\Awhsec_[0-9a-f]{32}\z/';
function verify_signature(string $rawBody, string $header, string $secret): bool
{
// Пустой или искажённый секрет — ошибка настройки, а не чужая подпись: HMAC с пустым ключом посчитает кто угодно.if (!preg_match(SECRET_PATTERN, $secret)) {
thrownew InvalidArgumentException('Секрет вебхука не задан или не похож на whsec_…');
}
$parts = [];
foreach (explode(',', $header) as $item) {
$pair = explode('=', trim($item), 2);
if (count($pair) === 2) {
$parts[trim($pair[0])] = trim($pair[1]);
}
}
$t = $parts['t'] ?? '';
$v1 = $parts['v1'] ?? '';
if (!preg_match('/^[0-9]+$/', $t) || !preg_match('/^[0-9a-f]{64}$/', $v1)) {
returnfalse;
}
if (abs(time() - (int) $t) > TOLERANCE_SEC) {
returnfalse;
}
// Ключ HMAC — секрет целиком, вместе с «whsec_».
$expected = hash_hmac('sha256', $t . '.' . $rawBody, $secret);
return hash_equals($expected, $v1);
}
// Без переменной getenv() вернёт false, то есть пустой секрет. PHP-FPM по умолчанию не передаёт скрипту окружение// (clear_env = yes): задайте секрет в пуле — env[EASYDONAT_WEBHOOK_SECRET] = whsec_… — или в настройках сайта.
$secret = (string) getenv('EASYDONAT_WEBHOOK_SECRET'); // whsec_…
$rawBody = file_get_contents('php://input'); // сырое тело — до json_decode
$header = $_SERVER['HTTP_X_EASYDONAT_SIGNATURE'] ?? '';
try {
$valid = verify_signature($rawBody, $header, $secret);
} catch (InvalidArgumentException $e) {
error_log('EasyDonat: ' . $e->getMessage());
// Сбой виден в журнале доставок. Повторы идут около 2 суток: настройте секрет за это время — событие придёт.
http_response_code(500);
exit;
}
if (!$valid) {
http_response_code(400);
exit('bad signature');
}
$event = json_decode($rawBody, true);
// Одно событие может прийти дважды: пропускайте уже обработанные $event['id'].// Источник правды — GET /orders/{order_id}. Ответить нужно за 10 секунд.
http_response_code(200);
# Python 3.8+ и Flask 2+import hashlib
import hmac
import os
import re
import time
from flask import Flask, abort, request
TOLERANCE_SEC = 5 * 60 # запросы старше 5 минут отклоняем
SECRET_RE = re.compile(r"whsec_[0-9a-f]{32}") # секрет вебхука: «whsec_» и 32 символа 0–9, a–fdef verify_signature(raw_body: bytes, header: str, secret: str) -> bool:
"""raw_body — тело запроса как есть (bytes), до разбора JSON."""# Пустой или искажённый секрет — ошибка настройки, а не чужая подпись: HMAC с пустым ключом посчитает кто угодно.ifnot isinstance(secret, str) ornot SECRET_RE.fullmatch(secret):
raise ValueError("Секрет вебхука не задан или не похож на whsec_…")
parts = {}
for item in (header or"").split(","):
key, sep, value = item.partition("=")
if sep:
parts[key.strip()] = value.strip()
t, v1 = parts.get("t", ""), parts.get("v1", "")
ifnot re.fullmatch(r"[0-9]+", t) ornot re.fullmatch(r"[0-9a-f]{64}", v1):
returnFalseif abs(time.time() - int(t)) > TOLERANCE_SEC:
returnFalse# Ключ HMAC — секрет целиком, вместе с «whsec_».
expected = hmac.new(secret.encode(), t.encode() + b"." + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, v1)
SECRET = os.environ["EASYDONAT_WEBHOOK_SECRET"] # whsec_…
app = Flask(__name__)
@app.post("/hooks/easydonat")
def easydonat_webhook():
raw = request.get_data() # сырое тело — до разбора JSON# Секрет не задан — verify_signature бросит ValueError, и Flask ответит 500.ifnot verify_signature(raw, request.headers.get("X-EasyDonat-Signature", ""), SECRET):
abort(400)
event = request.get_json()
# Одно событие может прийти дважды: пропускайте уже обработанные event["id"].# Источник правды — GET /orders/{order_id}. Ответить нужно за 10 секунд.return"", 200
После POST /account/webhook/secret/regenerate старый секрет перестаёт действовать сразу — в том числе для повторов. Обновите секрет у себя сразу после смены.
Ответ и повторы
Ответьте любым кодом 2xx в течение 10 секунд — доставка засчитана. Тяжёлую обработку делайте после ответа. Иначе уведомление придёт снова — через столько после предыдущей попытки:
1сразу
2+1 мин
3+5 мин
4+15 мин
5+1 ч
6+3 ч
7+6 ч
8+12 ч
9+24 ч
Всего 9 попыток, после последней неудачной доставка — failed (видно в журнале доставок).
Каждая попытка подписана заново — со свежим t, поэтому окно в 5 минут повторам не мешает.
Одно событие может прийти больше одного раза — отсеивайте повторы по id события: он одинаков у всех попыток. X-EasyDonat-Delivery у всех попыток одного события тоже один и тот же — это номер доставки в журнале, а номер попытки там — attempt. Повтор уже обработанного события — не ошибка: ответьте на него 2xx.
Если статус заказа сменился дважды между отправками, может прийти только последнее событие.
Если ваша интеграция уже работает с API автодоната с теми же разделами (/topups, /giftcards, /gamekeys, /steam-topup, /telegram), переход займёт немного времени: пути, имена полей и формат ответов совпадают — обычно достаточно сменить адрес и ключ.