Skip to content
Translated page. The English version is the source of truth.

Webhooks — 订单通知 ​

注册一个 HTTPS 端点,以便在您的订单完成并在链上确认的瞬间收到已签名的 Webhook。无需轮询,一旦收到通知即可继续您的业务流程(例如释放 USDT)。

我们会推送三种事件:

事件发送时机
delegation.confirmed能量租赁(1h / 5m)在链上确认
bandwidth.delegated带宽订单已完成
activation.confirmed地址激活在链上执行完毕

本页面涵盖了管理 API(创建 / 列出 / 编辑 / 轮换密钥 / 删除您的端点)以及我们向您推送的 Webhook 格式。

ℹ️ 角色说明。 您在此处管理端点。订单验证通过后,Netts 会异步执行推送 — 无需进行轮询。系统仅发送成功事件;失败和超时订单绝不会推送。

🔒 我们发送的每个哈希都预先经过链上验证。 仅当 Webhook 中包含的每个交易哈希都在区块中被查询到后,才会派发通知。如果哈希尚未入块,推送将被挂起,并每 30 秒重新检查一次,最长持续 5 分钟;如果始终未上链,该订单将不发送任何内容。您绝不会收到链上不存在的哈希。

端点基础 URL ​

https://netts.io/apiv2/webhooks

请求头 ​

请求头必填描述
Content-Type是 (针对 POST/PATCH)application/json
X-API-KEY是来自 Netts 控制面板的 API 密钥
X-Real-IP是来自白名单的 IP 地址

您的 user_id 是从 API 密钥派生的 — 无需手动传递。您只能查看和修改属于您自己的端点。


主端点与备用端点 ​

您最多可以注册两个端点,且每个端点都有一个 role(角色):

角色用途
primary每个 Webhook 默认推送的目标地址。
backup备用端点。仅当向 primary 推送失败且重试次数耗尽后才会使用。

单个已确认的订单仅生成一个 Webhook。这不是扇出式广播:同一个事件绝不会同时发送到两个地址。backup 端点的存在是为了提高可用性 — 如果您的主服务器不可达或持续返回非 2xx 响应,推送将转至备用端点,而不会被直接丢弃。

您创建的第一个端点将成为 primary,第二个将成为 backup。您可以显式传递 role,也可以稍后使用 PATCH 互换它们。

为什么不针对每种操作类型设置独立的 URL? 因为事件类型包含在请求体内部的 event 字段中。一套处理程序、一次签名校验,无需注册任何新端点即可直接接收新增的事件类型。


管理端点 ​

创建 — POST /apiv2/webhooks ​

注册一个新端点并返回仅显示一次的 secret(请妥善保存 — 它用于为您收到的每个 Webhook 进行签名)。

json
// request body — role is optional
{
    "url": "https://your-server.example/netts/delegation-hook",
    "role": "primary"
}

如果省略 role,将按顺序分配首个空闲角色:先是 primary,然后是 backup。

json
// 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;
  • 必须解析为公网地址 — 回环地址、私有地址(RFC1918)、链路本地地址(包括 169.254.169.254)以及其他不可路由网段均会被拒绝;
  • URL 中不得包含认证凭据(user:pass@…);
  • 长度最多 2048 个字符。

被拒绝的 URL 将返回 400。

您最多可以拥有两个端点 — 一个 primary 和一个 backup。尝试创建第三个端点将返回 409 (4090)。申请已被占用的 role 将返回 409 (4091) — 请使用 PATCH 互换角色,或先删除现有端点。

bash
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)。

json
{
    "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。

json
// request body (any subset)
{ "url": "https://your-server.example/netts/new-hook", "is_active": false }
json
// 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"} 将在单次事务中互换这两个角色 — 原主端点将变为备用端点。您绝不会处于没有主端点的状态,也无需针对另一个端点发起单独调用。

bash
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 并仅返回一次。新密钥对后续推送立即生效 — 无需执行其他操作。每个端点都拥有独立的密钥:轮换主端点的密钥不会影响备用端点。

json
// response 200
{
    "detail": {
        "code": 10000,
        "status": "rotated",
        "data": {
            "id": 1,
            "secret": "whsec_yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"
        }
    }
}

删除 — DELETE /apiv2/webhooks/{id} ​

硬删除该端点并释放其角色。返回 204(无响应体);传入其他用户的或不存在的 id 将返回 404。

bash
curl -X DELETE https://netts.io/apiv2/webhooks/1 \
  -H "X-API-KEY: your_api_key" -H "X-Real-IP: your_whitelisted_ip"

我们推送的 Webhooks ​

当您的某个订单完成时,Netts 会向您的 primary 端点发送一个 POST 请求。每个请求体均为 application/json(UTF-8);地址和哈希始终为完整值。

所有事件共有的字段:

字段类型描述
eventstring事件类型 — 用于您处理程序的路由键
delivery_idint推送 ID — 供您在接收端使用的去重键。同时在 X-Netts-Delivery 请求头中发送。
order_idstring您的订单 ID
order_typestring1h、5m、bandwidth 或 activation
tx_hashesstring[]该操作的所有交易哈希,每个哈希均在链上验证过
confirmed_atstringUTC ISO-8601 格式时间

delegation.confirmed — 能量租赁 ​

json
{
    "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_typestring1h 或 5m
receive_addressstring接收 Energy 的 TRON 地址
energy_amountint质押代理的 Energy 数量
tx_hashstring历史遗留字段,保留以实现兼容性:与 tx_hashes[0] 相同
delegation_timestampint?可选 — 仅在通过 Mongo 路径确认时存在

在新集成中推荐优先使用 tx_hashes — 原则上一个订单可能由多个交易完成。tx_hash 将继续保持可用。

bandwidth.delegated — 带宽订单 ​

json
{
    "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_secondsstring / int租赁时长,例如 1h / 3600
receive_addressstring接收 Bandwidth 的 TRON 地址
bandwidth_amountintBandwidth 单位数量(净值)
fulfillmentstring订单完成方式 — 详见下文

fulfillment 取值:

取值含义tx_hashes
delegated从我们的资金池中代理 Bandwidth1 个或多个哈希
trx_send通过向目标地址直接发送 TRX 代替代理来完成1 个或多个哈希
already_enough该地址已有足够的空闲 Bandwidth — 未在链上发送任何内容空

already_enough 是 tx_hashes 为空的唯一情况:订单已成功完结,但由于无需执行交易,因而没有生成交易。

activation.confirmed — 地址激活 ​

json
{
    "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_idstring激活订单 ID(数字字符串)
addressstring被激活的 TRON 地址
activation_typestringACC_CREATE (AccountCreateContract) 或 DIRECT (TRX 转账)
sourcestring来源标识。可能是一个服务标签,或者是需要该激活操作的 Energy 订单 ID

仅推送真实的激活事件。 如果地址本身已经被激活且未执行任何链上交易,则完全不会发送 Webhook。

同时需要激活操作的 Energy 订单会生成两个 Webhook — 一个 activation.confirmed 和一个 delegation.confirmed。它们是具有独立 delivery_id 的不同事件;请根据 event 字段进行路由。

我们发送的请求头:

请求头值
X-Netts-Event事件类型:delegation.confirmed、bandwidth.delegated 或 activation.confirmed
X-Netts-Deliverydelivery_id (去重)
X-Netts-Timestamp发送时的 Unix 秒级时间戳
X-Netts-Signaturesha256=<hex>,其中 hex = HMAC_SHA256(secret, "<timestamp>." + raw_body)
User-Agentnetts-webhook/1.0

验证签名 ​

签名采用 Stripe 规范方案(timestamp.body),基于我们发送的原始字节计算得出。请使用您的 secret 重新计算并进行恒定时间比对,若 X-Netts-Timestamp 超出 ±5 分钟 窗口则予以拒绝(防重放攻击)。

请使用接收到请求的端点所对应的密钥进行验签:主端点和备用端点具有各自独立的密钥。如果您的两个地址都由同一个处理程序提供服务,请根据请求到达的目标 URL 来选取密钥。

python
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):响应丢失可能导致重试,因此您可能会收到相同的事件。鉴于业务操作(释放 USDT)涉及资金安全:

  1. 必须进行去重 — 基于 delivery_id(和/或 order_id)幂等地处理每个事件;重复事件直接作为无操作(no-op)处理。
  2. 在执行任何涉及资金的操作前校验 HMAC — 在签名匹配且 X-Netts-Timestamp 处于有效期之前,不要信任请求体内容。
  3. 仅在持久化存储事件后再返回 2xx — 否则我们会(正确地)触发重试。

返回 2xx 响应以确认接收;任何非 2xx 响应或超时均会触发重试。

尝试顺序:

  1. 重试优先发送到您的 primary 端点。重试窗口取决于订单类型:5m 订单重试约 1 分钟,所有其他类型重试约 10 分钟。
  2. 如果超过时间窗口且您注册了 backup 端点,推送将转移至该备用端点,并重新开始重试计划 — 使用备用端点自身的密钥进行签名。
  3. 仅当备用端点也耗尽重试后,该次推送才会被标记为失败(dead)。

整个流程中使用相同的 delivery_id,因此在主端点失败随后在备用端点成功的消息,对您的去重逻辑而言仍然属于同一个事件。


错误代码参考 ​

代码描述HTTP 状态码
10000成功 (created / ok / updated / rotated)200 / 201
-已删除 (无响应体)204
4000无效 / 不安全的 Webhook URL (非 https、私有/回环地址、包含凭据、过长)400
-1无效的 API 密钥 / IP 不在白名单中401
-1未找到端点 (或不属于您)404
4090达到端点数量上限 (最多 2 个: primary, backup)409
4091请求的角色已被占用 — 请使用 PATCH 互换或删除现有端点409
4220无内容可更新 (PATCH 请求体为空)422
5003创建端点失败 (请重试)503

速率限制 ​

按 每个 API 密钥 进行限制(请求头 X-API-KEY):

周期限制
1 秒5 次请求
1 分钟150 次请求

超出速率限制 (429) ​

json
{ "message": "API rate limit exceeded" }

注意事项 ​

  • 密钥仅显示一次 — 在创建和轮换时提供。使用 GET/LIST 绝不会返回该密钥。如果不慎丢失,请通过轮换操作获取新密钥。
  • 两个端点,非扇出式广播: 一个 primary 和一个 backup。每个确认的订单只生成一个 Webhook 并推送到主端点;备用端点仅在主端点重试耗尽后使用。
  • 零停机时间更改 URL: 将新地址注册为 backup,验证通过后,使用 PATCH 将其变更为 primary — 该角色互换操作是原子性的。
  • 暂停: PATCH … {"is_active": false} 可停止推送且无需删除端点。
  • 仅推送成功事件: delegation.confirmed、bandwidth.delegated、activation.confirmed。没有失败事件 — 失败或超时的订单不会生成 Webhook。
  • 未来可能会添加新的事件类型。 请根据 event 字段进行路由,并忽略您尚不处理的类型 — 无需注册任何新端点即可接收它们。
  • 哈希在推送前会在链上进行验证(参见顶部说明):Webhook 要么携带全部已入块的哈希,要么完全不发送。
  • URL 安全性校验: 在注册和每次编辑时均会验证 URL 的 SSRF 安全性;推送端在发送时也会重新进行验证。