Вебхуки — сповіщення про замовлення
Зареєструйте HTTPS-ендпоінт, щоб отримувати підписаний вебхук у той самий момент, коли одне з ваших замовлень виконано та підтверджено в блокчейні. Замість опитування (polling), ви продовжуєте свій робочий процес (наприклад, виплачуєте USDT) одразу після надходження сповіщення.
Доставляються три події:
| Подія | Відправляється, коли |
|---|---|
delegation.confirmed | Оренда Energy (1h / 5m) підтверджена в блокчейні |
bandwidth.delegated | Замовлення Bandwidth виконано |
activation.confirmed | Активація адреси виконана в блокчейні |
Ця сторінка охоплює API керування (створення / перегляд списку / редагування / ротація секрету / видалення ваших ендпоінтів) та формат вебхуків, які ми вам надсилаємо.
ℹ️ Ролі. Тут ви керуєте своїми ендпоінтами. Доставка виконується системою Netts асинхронно після підтвердження замовлення — нічого опитувати не потрібно. Надсилаються лише успішні події; помилки та таймаути ніколи не доставляються.
🔒 Кожен хеш, який ми надсилаємо, спочатку перевіряється в блокчейні. Вебхук відправляється лише після того, як кожен хеш транзакції в ньому знайдено в блоці. Якщо хеша в блоці ще немає, доставка призупиняється та перевіряється повторно кожні 30 секунд терміном до 5 хвилин; якщо він так і не потрапить туди, для цього замовлення нічого не надсилається. Ви ніколи не отримаєте хеш, якого не існує в блокчейні.
Базовий URL ендпоінта
https://netts.io/apiv2/webhooksЗаголовки запиту
| Заголовок | Обов'язковий | Опис |
|---|---|---|
| Content-Type | Так (для POST/PATCH) | application/json |
| X-API-KEY | Так | Ваш API ключ із панелі керування Netts |
| X-Real-IP | Так | IP-адреса з вашого білого списку |
Ваш user_id визначається на основі API ключа — передавати його не потрібно. Ви можете бачити та змінювати лише власні ендпоінти.
Основний та резервний ендпоінт
Ви реєструєте щонайбільше два ендпоінти, і кожен із них має роль role:
| Роль | Призначення |
|---|---|
primary | Адреса, на яку доставляється кожен вебхук. |
backup | Резерв. Використовується лише тоді, коли доставка на primary зазнала невдачі після вичерпання спроб. |
Одне підтверджене замовлення генерує один вебхук. Це не віялова розсилка: одна й та сама подія ніколи не надсилається на обидві адреси одночасно. Ендпоінт backup існує для стійкості — якщо ваш основний хост недоступний або постійно повертає код, відмінний від 2xx, доставка перемикається на резервний замість того, щоб бути скасованою.
Перший ендпоінт, який ви створюєте, стає primary, другий — backup. Ви можете вказати role явно або поміняти їх місцями пізніше за допомогою PATCH.
Чому немає окремого URL для кожного типу операцій? Тому що тип події передається всередині тіла запиту, у полі
event. Один обробник, одна перевірка підпису, і нові типи подій починають надходити без реєстрації чогось нового з вашого боку.
Керування ендпоінтами
Створення — POST /apiv2/webhooks
Реєструє новий ендпоінт і повертає secret, який відображається лише один раз (збережіть його — він підписує кожен вебхук, який ви отримуєте).
// request body — role is optional
{
"url": "https://your-server.example/netts/delegation-hook",
"role": "primary"
}Якщо пропустити role, призначається перша вільна: спочатку primary, потім backup.
// response 201
{
"detail": {
"code": 10000,
"status": "created",
"data": {
"id": 1,
"url": "https://your-server.example/netts/delegation-hook",
"secret": "whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"role": "primary",
"is_active": true,
"created_at": "2026-01-01T00:00:00"
}
}
}Вимоги до URL (перевіряються під час створення та кожного редагування):
- обов'язково
https; - має визначатися в публічну адресу — loopback, приватні (RFC1918), link-local (включно з
169.254.169.254) та інші немаршрутизовані діапазони відхиляються; - без облікових даних в URL (
user:pass@…); - довжина до 2048 символів.
Відхилений URL повертає 400.
Ви можете мати два ендпоінти — один primary та один backup. Спроба створити третій повертає 409 (4090). Запит role, яка вже зайнята, повертає 409 (4091) — поміняйте ролі місцями за допомогою PATCH або спочатку видаліть наявний.
curl -X POST https://netts.io/apiv2/webhooks \
-H "Content-Type: application/json" \
-H "X-API-KEY: your_api_key" \
-H "X-Real-IP: your_whitelisted_ip" \
-d '{"url": "https://your-server.example/netts/delegation-hook", "role": "primary"}'Список — GET /apiv2/webhooks
Повертає ваші ендпоінти (secret тут ніколи не повертається).
{
"detail": {
"code": 10000,
"status": "ok",
"data": {
"endpoints": [
{
"id": 1,
"url": "https://your-server.example/netts/delegation-hook",
"role": "primary",
"is_active": true,
"created_at": "2026-01-01T00:00:00",
"updated_at": "2026-01-01T00:00:00"
},
{
"id": 2,
"url": "https://backup.example/netts/delegation-hook",
"role": "backup",
"is_active": true,
"created_at": "2026-01-01T00:00:00",
"updated_at": "2026-01-01T00:00:00"
}
],
"count": 2,
"max_endpoints": 2,
"roles": ["primary", "backup"]
}
}
}Отримання одного — GET /apiv2/webhooks/{id}
Така сама структура, як і в елемента списку (без secret). Чужий або неіснуючий id повертає 404.
Редагування — PATCH /apiv2/webhooks/{id}
Зміна url, is_active та/або role. Можна надсилати будь-яку підмножину; порожнє тіло повертає 422. Змінений url повторно перевіряється (https / SSRF). Чужий або неіснуючий id повертає 404.
// request body (any subset)
{ "url": "https://your-server.example/netts/new-hook", "is_active": false }// response 200
{
"detail": {
"code": 10000,
"status": "updated",
"data": {
"id": 1,
"url": "https://your-server.example/netts/new-hook",
"role": "primary",
"is_active": false,
"created_at": "2026-01-01T00:00:00",
"updated_at": "2026-01-01T00:00:01"
}
}
}Підвищення резервного. Відправлення {"role": "primary"} на ваш резервний ендпоінт міняє обидві ролі місцями в одній транзакції — старий основний стає резервним. Ви ніколи не залишитеся без основної адреси, і окремий виклик для іншого ендпоінта не потрібен.
curl -X PATCH https://netts.io/apiv2/webhooks/2 \
-H "Content-Type: application/json" \
-H "X-API-KEY: your_api_key" \
-H "X-Real-IP: your_whitelisted_ip" \
-d '{"role": "primary"}'Встановіть
is_active: false, щоб призупинити доставку без видалення ендпоінта;true— для відновлення. Призупинення вашогоprimaryне підвищує резервний — доставка все одно спрямовується на основний. Поміняйте ролі місцями, якщо хочете, щоб резервний перехопив трафік.
Ротація секрету — POST /apiv2/webhooks/{id}/rotate-secret
Генерує новий secret і повертає його один раз. Новий секрет набирає чинності негайно для наступних доставок — жодних додаткових дій не потрібно. Кожен ендпоінт має свій власний секрет: ротація секрету основного ендпоінта не змінює секрет резервного.
// response 200
{
"detail": {
"code": 10000,
"status": "rotated",
"data": {
"id": 1,
"secret": "whsec_yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"
}
}
}Видалення — DELETE /apiv2/webhooks/{id}
Безповоротно видаляє ендпоінт і звільняє його роль. Повертає 204 (без тіла); чужий або неіснуючий id повертає 404.
curl -X DELETE https://netts.io/apiv2/webhooks/1 \
-H "X-API-KEY: your_api_key" -H "X-Real-IP: your_whitelisted_ip"Вебхуки, які ми доставляємо
Коли одне з ваших замовлень виконано, Netts надсилає POST на ваш ендпоінт primary. Кожне тіло має тип application/json (UTF-8); адреси та хеші завжди надаються повністю.
Поля, спільні для всіх подій:
| Поле | Тип | Опис |
|---|---|---|
event | string | Тип події — ключ маршрутизації для вашого обробника |
delivery_id | int | ID доставки — ключ дедуплікації на вашому боці. Також надсилається в заголовку X-Netts-Delivery. |
order_id | string | ID вашого замовлення |
order_type | string | 1h, 5m, bandwidth або activation |
tx_hashes | string[] | Усі хеші транзакцій операції, кожен із яких підтверджений у блокчейні |
confirmed_at | string | UTC ISO-8601 |
delegation.confirmed — оренда Energy
{
"event": "delegation.confirmed",
"delivery_id": 1,
"order_id": "1Hxxxxxxxxxx",
"order_type": "1h",
"receive_address": "TXXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"energy_amount": 65000,
"tx_hash": "0000000000000000000000000000000000000000000000000000000000000000",
"tx_hashes": ["0000000000000000000000000000000000000000000000000000000000000000"],
"delegation_timestamp": 1700000000000,
"confirmed_at": "2026-01-01T00:00:00Z"
}| Поле | Тип | Опис |
|---|---|---|
order_type | string | 1h або 5m |
receive_address | string | TRON адреса, яка отримала Energy |
energy_amount | int | Кількість делегованої Energy |
tx_hash | string | Застаріле поле, збережене для сумісності: те саме, що й tx_hashes[0] |
delegation_timestamp | int? | Необов'язково — присутнє лише за умови підтвердження через шлях Mongo |
У нових інтеграціях надавайте перевагу
tx_hashes— замовлення в теорії може бути виконане більш ніж однією транзакцією.tx_hashпродовжуватиме працювати.
bandwidth.delegated — замовлення Bandwidth
{
"event": "bandwidth.delegated",
"delivery_id": 2,
"order_id": "B1Hxxxxxxxxxxxxx",
"order_type": "bandwidth",
"rental_label": "1h",
"rental_seconds": 3600,
"receive_address": "TXXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"bandwidth_amount": 400,
"fulfillment": "delegated",
"tx_hashes": ["0000000000000000000000000000000000000000000000000000000000000000"],
"confirmed_at": "2026-01-01T00:00:00Z"
}| Поле | Тип | Опис |
|---|---|---|
rental_label / rental_seconds | string / int | Тривалість оренди, наприклад 1h / 3600 |
receive_address | string | TRON адреса, яка отримала Bandwidth |
bandwidth_amount | int | Одиниці Bandwidth (нетто) |
fulfillment | string | Спосіб виконання замовлення — див. нижче |
Значення fulfillment:
| Значення | Значення | tx_hashes |
|---|---|---|
delegated | Bandwidth делеговано з нашого пулу | 1+ хешів |
trx_send | Виконано шляхом надсилання TRX на адресу замість делегування | 1+ хешів |
already_enough | Адреса вже мала достатньо вільного Bandwidth — у блокчейн нічого не надсилалося | порожньо |
already_enough — це єдиний випадок, коли tx_hashes порожній: замовлення успішно закрите, але транзакція відсутня, оскільки в ній не було потреби.
activation.confirmed — активація адреси
{
"event": "activation.confirmed",
"delivery_id": 3,
"order_id": "123456",
"order_type": "activation",
"address": "TXXxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"activation_type": "ACC_CREATE",
"source": "telegram_bot",
"tx_hashes": ["0000000000000000000000000000000000000000000000000000000000000000"],
"confirmed_at": "2026-01-01T00:00:00Z"
}| Поле | Тип | Опис |
|---|---|---|
order_id | string | ID замовлення на активацію (числовий рядок) |
address | string | TRON адреса, яка була активована |
activation_type | string | ACC_CREATE (AccountCreateContract) або DIRECT (переказ TRX) |
source | string | Маркер джерела. Або сервісний тег, або ID замовлення Energy, яке вимагало активації |
Доставляються лише реальні активації. Якщо виявилося, що адреса вже активна і транзакція не здійснювалася, вебхук узагалі не надсилається.
Замовлення Energy, яке також вимагало активації, генерує два вебхуки — один
activation.confirmedта одинdelegation.confirmed. Це окремі події з окремимиdelivery_id; маршрутизуйте їх за полемevent.
Заголовки, які ми надсилаємо:
| Заголовок | Значення |
|---|---|
X-Netts-Event | Тип події: delegation.confirmed, bandwidth.delegated або activation.confirmed |
X-Netts-Delivery | delivery_id (дедуплікація) |
X-Netts-Timestamp | unix-час у секундах на момент відправлення |
X-Netts-Signature | sha256=<hex>, hex = HMAC_SHA256(secret, "<timestamp>." + raw_body) |
User-Agent | netts-webhook/1.0 |
Перевірка підпису
Підпис відповідає схемі Stripe (timestamp.body), обчислюється на основі сирих байтів (raw bytes), які ми надсилаємо. Перерахуйте його за допомогою вашого secret, порівняйте за сталий час (constant-time) і відхиліть, якщо X-Netts-Timestamp виходить за межі вікна ±5 хвилин (захист від повторного відтворення).
Підписуйте секретом того ендпоінта, який отримав запит: основний і резервний мають окремі секрети. Якщо обидві ваші адреси обслуговуються одним обробником, обирайте секрет залежно від URL, на який надійшов запит.
import hmac, hashlib, time
def verify(raw_body: bytes, sig_header: str, ts_header: str, secret: str) -> bool:
# freshness (anti-replay)
if abs(time.time() - int(ts_header)) > 300:
return False
signed = f"{ts_header}.".encode() + raw_body
expected = "sha256=" + hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, sig_header)
# Flask example:
# ok = verify(request.get_data(),
# request.headers["X-Netts-Signature"],
# request.headers["X-Netts-Timestamp"], SECRET)Семантика доставки (важливо — at-least-once)
Доставка здійснюється за принципом щонайменше один раз (at-least-once): втрачена відповідь може спричинити повторну спробу, тому ви можете отримати одну й ту саму подію двічі. Оскільки бізнес-дія (виплата USDT) пов'язана з коштами:
- Дедуплікація обов'язкова — обробляйте кожну подію ідемпотентно за
delivery_id(та/абоorder_id); повтор є порожньою операцією (no-op). - Перевіряйте HMAC перед будь-якою дією з коштами — не довіряйте тілу запиту, доки підпис не зійдеться і
X-Netts-Timestampне буде актуальним. - Повертайте 2xx лише після того, як надійно зберегли подію — інакше ми (цілком виправдано) повторимо спробу.
Відповідайте 2xx для підтвердження; будь-який статус, відмінний від 2xx, або таймаут викликає повторну спробу.
Порядок спроб:
- Повторні спроби спрямовуються на ваш ендпоінт
primary. Вікно залежить від типу замовлення: для замовлень5mповтори тривають ~1 хвилину, для всіх інших типів — ~10 хвилин. - Якщо вікно вичерпано і ви зареєстрували
backup, доставка перемикається туди, і графік повторів починається спочатку — з підписом власним секретом ендпоінта backup. - Лише після того, як спроби для резервного також буде вичерпано, доставка позначається як невдала.
Протягом усього процесу використовується один і той самий delivery_id, тому повідомлення, яке спочатку зазнало невдачі на основному, а потім успішно пройшло на резервному, все одно залишається однією подією для вашої логіки дедуплікації.
Довідник кодів помилок
| Код | Опис | HTTP-статус |
|---|---|---|
10000 | Успіх (created / ok / updated / rotated) | 200 / 201 |
- | Видалено (без тіла) | 204 |
4000 | Некоректний / небезпечний URL вебхука (не https, приватний/loopback, облікові дані, занадто довгий) | 400 |
-1 | Недійсний API ключ / IP-адреси немає в білому списку | 401 |
-1 | Ендпоінт не знайдено (або він не ваш) | 404 |
4090 | Досягнуто ліміту ендпоінтів (максимум 2: primary, backup) | 409 |
4091 | Запитувана роль уже зайнята — поміняйте місцями через PATCH або видаліть наявний ендпоінт | 409 |
4220 | Немає даних для оновлення (PATCH з порожнім тілом) | 422 |
5003 | Не вдалося створити ендпоінт (спробуйте ще раз) | 503 |
Ліміти запитів (Rate Limits)
Обмежується на рівні API ключа (заголовок X-API-KEY):
| Період | Ліміт |
|---|---|
| 1 секунда | 5 запитів |
| 1 хвилина | 150 запитів |
Перевищення ліміту запитів (429)
{ "message": "API rate limit exceeded" }Примітки
- Секрет відображається один раз — під час створення та під час ротації. Він ніколи не повертається у відповідях
GET/LIST. Втратили його? Зробіть ротацію, щоб отримати новий. - Два ендпоінти, а не віялова розсилка: один
primaryта одинbackup. Кожне підтверджене замовлення генерує один вебхук, який доставляється на основний; резервний використовується лише тоді, коли спроби для основного вичерпано. - Зміна URL без простою: зареєструйте нову адресу як
backup, перевірте її, а потім виконайтеPATCH, змінивши її наprimary— заміна відбувається атомарно. - Призупинення:
PATCH … {"is_active": false}зупиняє доставку без втрати ендпоінта. - Лише успішні події:
delegation.confirmed,bandwidth.delegated,activation.confirmed. Подій про помилку немає — невдале замовлення або замовлення з вичерпаним таймаутом не створює вебхука. - З часом можуть додаватися нові типи подій. Маршрутизуйте за полем
eventта ігноруйте типи, які ви ще не обробляєте — вам не потрібно реєструвати щось нове, щоб почати їх отримувати. - Хеші перевіряються в блокчейні перед доставкою (див. примітку на початку): вебхук або містить хеші, які вже є в блоці, або не надсилається зовсім.
- URL-адреси перевіряються на безпеку щодо SSRF під час реєстрації та при кожному редагуванні; сервіс доставки повторно перевіряє їх під час відправлення.