Меню документации

Webhooks

TEnergy поддерживает отправку асинхронных уведомлений о событиях на ваш сервер через Webhooks. Состояния заказов совпадают с API и кабинетом: created → paid → allocating → delegated → confirmed → active → expired | reclaimed, с терминальными ветками ошибок failed и refunded. Частичное исполнение не является отдельным статусом, а выражается флагом partial: true и объемом delivered_amount.

Управление эндпоинтами

Метод APIДействие
POST /v1/webhooksРегистрация нового эндпоинта. Секрет подписи возвращается ровно один раз.
GET /v1/webhooksСписок эндпоинтов. Никогда не возвращает секреты.
PATCH /v1/webhooks/{id}Изменение URL, списка событий, флага активности is_active или смены роли role.
POST /v1/webhooks/{id}/rotate-secretРотация секрета. Новый секрет вступает в силу мгновенно и показывается один раз.
POST /v1/webhooks/{id}/testОтправка тестового события; возвращает отчет о том, что ответил ваш сервер (включая первые 512 байт тела ответа).
DELETE /v1/webhooks/{id}Полное удаление эндпоинта; недоставленные события для него сбрасываются.
  • Два эндпоинта: основной и резервный. Вы можете настроить один primary (основной) и один backup (резервный) эндпоинт. Все события отправляются на основной; резервный подключается только после исчерпания всех попыток на основном. Каждое событие сохраняет постоянный delivery_id при переходе между эндпоинтами для корректной дедупликации.
  • Бесшовная смена URL без простоя: зарегистрируйте новый URL с ролью backup, проверьте тестовой отправкой, а затем вызовите PATCH и установите role: "primary".
  • Локальная разработка: используйте публичный туннель (например, ngrok) — валидатор URL отклоняет локальные и приватные IP, поэтому зарегистрировать localhost напрямую нельзя.

Типы событий

СобытиеУсловие отправкиПоля объекта data
order.confirmedДелегирование подтверждено в блокчейне TRON и проверено на адресе получателя.order_id, client_order_id, batch_id, subscription_id, resource, amount, delivered_amount, partial, tier, duration_seconds, receiver, delegate_hash, delegate_hashes[], delegated_at, expires_at, total_amount_sun, refunded_amount_sun, activation
order.failedЗаказ не удалось доставить. Терминальный статус; списанные средства полностью возвращены.order_id, client_order_id, receiver, resource, amount, tier, failure{code,slug,message}, charged_amount_sun, refunded_amount_sun, refund_complete
order.expiredСрок аренды истек, делегированные ресурсы автоматически отозваны блокчейном.аналогично order.reclaimed, содержит expired_at.
order.reclaimedАренда досрочно отозвана через POST /v1/orders/{id}/reclaim.order_id, client_order_id, receiver, resource, amount, reclaim_hash, reclaimed_at, refunded_amount_sun
order.refundedВыполненный заказ был отменен с возвратом средств на баланс (терминальный статус).order_id, client_order_id, receiver, resource, amount, delivered_amount, partial, reason, charged_amount_sun, refunded_amount_sun, refund_complete, refunded_at
batch.completedКаждый получатель в пакетном заказе достиг финального статуса. Отправляется один раз на пакет.batch_id, client_batch_id, status, summary{total,completed,partial,failed,insufficient_funds,cancelled}, charged_amount_sun, finished_at
subscription.refilledАвтопополнение баланса энергии для отслеживаемого адреса успешно выполнено.subscription_id, order_id, receiver, resource, amount, tier, trigger_available, threshold_amount, total_amount_sun, refills_today
subscription.suspendedПодписка приостановлена (например, из-за нехватки баланса).subscription_id, receiver, reason, required_sun, available_sun
subscription.pausedПакетная подписка приостановила делегирование.subscription_id, receiver, reason ∈ billing_failed, reserve_exhausted, user
subscription.chargedСписана ежедневная плата за тариф подписки.subscription_id, receiver, plan, amount_sun, next_billing_at
balance.creditedДепозит подтвержден и зачислен на баланс аккаунта.reason, currency, amount_sun, tx_hash, confirmations, balance_sun, reference_id, asset, usdt_amount, rate
balance.lowБаланс опустился ниже заданного порога предупреждения.balance_sun, threshold_sun, estimated_orders_remaining
deposit_address.rotatedПоддержка сменила депозитный адрес аккаунта.address (новый адрес), retired_address (выведенный из эксплуатации), retired_credit_until (срок зачисления на старый адрес), rotated_at
  • Отказ — это тоже событие: мы отправляем order.failed. Уведомление только об успехах вынуждает клиентов опрашивать API для выявления сбоев. Маршрутизируйте логику по event и игнорируйте неизвестные типы — добавление новых событий не должно ломать ваш сервис.
  • order.confirmed: обязательно проверяйте partial. При частичном исполнении приходит это же событие с флагом partial: true, объемом delivered_amount < amount и суммой возврата refunded_amount_sun.
  • delegate_hashes: массив транзакций в сети (при наборе объема из нескольких кошельков). Каждый хэш верифицируется в блокчейне перед отправкой вебхука, отправка несуществующих хэшей исключена.

Структура полезной нагрузки (Payload)

Общая структура для всех событий:

json
{
  "event": "order.confirmed",
  "event_id": "evt_01J9ZB3F5HJK",
  "event_version": 1,
  "created_at": "2026-09-11T18:04:08.100Z",
  "account_id": "acc_01J9Z4K2M7Q8",
  "network": "mainnet",
  "test": false,
  "data": { }
}
ПолеНазначение
eventИмя типа события для внутренней маршрутизации в вашем коде.
event_idУникальный ID события для дедупликации. Также передается в заголовке X-Event-Id.
event_versionВерсия схемы данных, в настоящее время 1.
created_atТочное время возникновения события (UTC). При ретраях сохраняется исходное время.
account_idID аккаунта платформы.
networkСеть: mainnet или nile. Защищает от попадания тестовых событий в боевой контур.
testРавно true только для тестовых доставок из POST /v1/webhooks/{id}/test. Никогда не выполняйте реальных действий с товаром или деньгами при test: true.
dataСпецифичный для события объект с данными.

Заголовки доставки

ЗаголовокЗначение
Content-Typeapplication/json; charset=utf-8
X-Event-IdУникальный ID события. Не меняется при ретраях и при переходе primary/backup.
X-Event-TypeТип события, дублирует поле event.
X-Event-VersionВерсия схемы полезной нагрузки.
X-Delivery-IdУникальный ID конкретной попытки доставки. Меняется при каждом ретрае.
X-Delivery-AttemptНомер попытки, начиная с 1.
X-API-TIMESTAMPМетка времени отправки (Unix seconds).
X-API-SIGNbase64(HMAC_SHA256(endpoint_secret, timestamp + "." + raw_body))
User-Agenttenergy-webhook/1

Выполняйте дедупликацию строго по X-Event-Id (или event_id), а не по X-Delivery-Id.

Проверка подписи

Спецификация: X-API-SIGN = base64(HMAC_SHA256(endpoint_secret, X-API-TIMESTAMP + "." + raw_body)), вычисляется строго над сырыми байтами тела запроса в том виде, в котором они были получены — любое изменение форматирования JSON ломает подпись. Допустимое окно рассинхронизации часов: ±300 секунд.

javascript
import crypto from 'node:crypto';
const MAX_SKEW_SECONDS = 300;

export function verifyWebhook(rawBody, headers, secret) {
  const timestamp = headers['x-api-timestamp'], signature = headers['x-api-sign'];
  if (!timestamp || !signature) return false;
  const skew = Math.abs(Date.now() / 1000 - Number(timestamp));   // защита от повторов
  if (!Number.isFinite(skew) || skew > MAX_SKEW_SECONDS) return false;
  const signed = Buffer.concat([Buffer.from(`${timestamp}.`), rawBody]);
  const expected = crypto.createHmac('sha256', secret).update(signed).digest('base64');
  const a = Buffer.from(expected), b = Buffer.from(signature);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

Три правила проверки:

  1. Сначала проверка подписи, затем парсинг: не передавайте непроверенный JSON в бизнес-логику.
  2. Сравнение за константное время: используйте crypto.timingSafeEqual или hmac.compare_digest для защиты от атак по времени.
  3. Проверка метки времени: отклоняйте запросы с разницей более 300 секунд для защиты от атак повторного воспроизведения.

Расписание повторных попыток

Доставка строится по принципу At-least-once (как минимум один раз). Ваш сервер должен ответить статусом 2xx; любой статус, отличный от 2xx, обрыв соединения или ответ дольше 10 секунд считается сбоем и запускает повторные попытки:

Попытка12345678910
Пауза после предыдущейсразу15 с30 с3 мин10 мин20 мин30 мин1 ч3 ч6 ч

Общий интервал повторов составляет около 11 часов. Для сверхкоротких тиров (5m, 15m) повторы прекращаются после 4 попытки (около 4 минут). Если основной эндпоинт исчерпал попытки и настроен резервный, доставка переключается на резервный, и расписание запускается с 1 попытки. Тестовые события никогда не повторяются.

Требования к сервису-получателю

  1. Дедупликация по event_id: сохраняйте полученные идентификаторы; повторный запрос должен возвращать 200 без повторного выполнения бизнес-логики.
  2. Проверяйте HMAC перед отгрузкой: никогда не исполняйте заказ клиента до успешной проверки подписи вебхука.
  3. Возвращайте 2xx только после надежного сохранения события: подтверждение до сохранения превратит наш ретрай в утерянное вами событие.
  4. Не полагайтесь исключительно на вебхуки: периодически выполняйте сверку по расписанию через GET /v1/orders?status=confirmed&created_after=….
  5. Отвечайте в течение 10 секунд: принимайте событие, сохраняйте в очередь и сразу возвращайте 200; тяжелую обработку выполняйте в фоне.

    ↑ ↓ — выбрать · Enter — открыть · Esc — закрыть