Вебхуки — уведомления о заказах
Зарегистрируйте 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 завершается ошибкой после исчерпания всех попыток. |
Один подтвержденный заказ порождает один вебхук. Это не веерная рассылка (fan-out): одно и то же событие никогда не отправляется на оба адреса одновременно. Эндпоинт 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 | Идентификатор доставки — ключ дедупликации на вашей стороне. Также передается в заголовке X-Netts-Delivery. |
order_id | string | Идентификатор вашего заказа |
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 | Идентификатор заказа на активацию (числовая строка) |
address | string | Активированный TRON-адрес |
activation_type | string | ACC_CREATE (AccountCreateContract) или DIRECT (перевод TRX) |
source | string | Маркер источника. Либо сервисный тег, либо идентификатор заказа 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), вычисляясь от сырых байтов, которые мы отправляем. Пересчитайте ее с вашим secret, сравните с постоянным временем выполнения и отклоните запрос, если X-Netts-Timestamp выходит за пределы окна в ±5 минут (защита от повторов/replay attacks).
Подписывайте секретом того эндпоинта, который принял запрос: у основного и резервного эндпоинтов разные секреты. Если оба ваших адреса обрабатываются одним и тем же сервисом, выбирайте секрет по 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, доставка переключается на него, а цикл повторных попыток запускается заново — с подписью собственным секретом резервного эндпоинта. - Только после того, как попытки обращения к резервному эндпоинту также исчерпаны, доставка помечается как неудавшаяся.
Один и тот же 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-безопасности при регистрации и при каждом изменении; сервис доставки выполняет повторную валидацию в момент отправки.