文档目录

Webhooks 回调

TEnergy 支持通过 Webhook 实时向您的服务推送异步事件通知。订单状态流转与 API 和控制台保持完全一致:created → paid → allocating → delegated → confirmed → active → expired | reclaimed,异常时流转至终止状态 failed 或 refunded。部分交付并不是一个独立状态,而是通过 partial: true 标记及实际交付量 delivered_amount 体现。

管理 Webhook 端点

API 接口作用说明
POST /v1/webhooks注册新端点。签名密钥 secret 仅在创建时返回一次。
GET /v1/webhooks查询已注册的端点列表。绝不返回 secret。
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: 先将新地址注册为 backup,通过测试事件验证可用性,然后调用 PATCH 将其切换为 role: "primary" 即可无缝切换。
  • 本地调试: 请使用公网隧道(如 ngrok)—— 系统校验器会自动拒绝回环与私有内网 IP,因此无法直接注册 localhost。

事件类型列表

事件名称触发时机核心数据字段 (data)
order.confirmed链上委托已成功确认且接收地址额度已验证。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 事件。仅推送成功事件会迫使客户端通过轮询去检查最重要的失败情况。请按 event 分发路由,并忽略未知的事件类型 —— 新增事件类型不应导致您的服务异常。
  • order.confirmed 中必须检查 partial 字段:部分交付时仍会发送此事件,附带 partial: true、delivered_amount < amount 以及已自动原路退回的差额 refunded_amount_sun。
  • delegate_hashes 为权威数组(当自营多个质押地址共同撮合一单时会有多笔交易);delegate_hash 取第一笔。每个 Hash 在推送前都经过链上实际确认,绝不提前虚报不存在的交易哈希。

载荷通用格式

外层通用信封结构对所有事件完全一致:

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事件唯一标识,用于幂等去重。亦包含在 X-Event-Id 请求头中。
event_version数据格式版本号,当前统一为 1。
created_at事件真实发生的时间(UTC 时间戳)。重试推送保持初始发生时间不变。
account_id归属的平台账户 ID。
network所在网络:mainnet(主网)或 nile(测试网)。请据此防范测试网事件误入生产业务系统。
test仅在由 POST /v1/webhooks/{id}/test 触发时为 true。当 test === true 时切勿执行真实发货。
data事件专属数据载荷。

投递请求头

请求头说明
Content-Typeapplication/json; charset=utf-8
X-Event-Id该事件的全局唯一 ID。重试或主备流转保持不变。
X-Event-Type事件类型,与请求体内的 event 一致。
X-Event-Version载荷版本号,目前统一为 1。
X-Delivery-Id本次投递尝试的唯一 ID。每次重试都会生成新的 ID。
X-Delivery-Attempt投递尝试计数,从 1 开始递增。
X-API-TIMESTAMP发送时的 Unix 秒级时间戳。
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)),其中 raw_body 必须为接收到的原始未经解析的字节串 —— 任何 JSON 序列化变更或多余空格都会导致签名校验失败。时间漂移窗口为 ±300 秒。请使用接收该请求的端点对应的 Secret(主端点与备用端点拥有独立的 Secret)。

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);
}

Python 验证逻辑:使用 hmac.compare_digest 比较 base64.b64encode(hmac.new(secret.encode(), f"{timestamp}.".encode() + raw_body, hashlib.sha256).digest())。

三大验证守则:

  1. 先验签再解析: 绝不在业务逻辑中处理未经验签的 JSON。
  2. 常量时间比对: 必须使用 crypto.timingSafeEqual 或 hmac.compare_digest 防范时序侧信道攻击。
  3. 校验时间戳: 验证时间戳与当前时间是否在 ±300 秒以内,防止重放攻击。

重试机制与时间表

投递采用 At-least-once(至少一次) 语义。您的服务必须返回 HTTP 2xx 状态码以表示成功签收;任何非 2xx 响应、连接超时或响应时间超过 10 秒 均被视为投递失败并触发阶梯重试。

尝试次数12345678910
与上一轮的间隔立即15 秒30 秒3 分钟10 分钟20 分钟30 分钟1 小时3 小时6 小时

整个重试周期约为 11 小时。超短时订单(5m、15m)在第 4 次尝试(约 4 分钟)后自动停止重试 —— 此时租用已结束,延迟交付已无实际意义。若主端点重试耗尽且配置了备用端点,任务将切换至备用端点并重新从第 1 次时间表开始;仅当备用端点也耗尽重试后,投递才彻底宣告失败。您可在控制台中查看投递历史;测试事件从不重试。

接收端最佳实践

  1. 基于 event_id 去重: 将处理过的 ID 存入缓存或数据库;遇到重复请求直接响应 200。
  2. 先验签再履约: 务必在通过 HMAC 校验后再对下游用户发货。
  3. 先持久化再应答 2xx: 确保事件已落库后再向我们返回 2xx。
  4. 不要将 Webhook 视为唯一数据源: Webhook 是轮询的优化方案。建议配置定时对账任务,比对 GET /v1/orders?status=confirmed&created_after=…。
  5. 在 10 秒内快速应答: 收到请求后快速确认存储并返回 200,复杂的业务履约应放入异步队列中处理。

    ↑ ↓ 切换 · Enter 打开 · Esc 关闭