> For the complete documentation index, see [llms.txt](https://docs.catfee.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.catfee.io/getting-started/webhook.md).

# 回调（Webhook）

### 概览

当平台发生事件时，会通过 HTTP `POST` 调用你配置的回调地址（callbackUrl）。你需要：

1. 接收并解析 JSON 数据；
2. 根据 `X-EVENT-TYPE` 识别事件类型；
3. 成功处理后 **返回 `HTTP 200`** ；
4. 否则平台将按重试策略重新通知。

***

### 配置回调地址

* **入口位置**：进入 **用户中心 → API 设置** 页面。
* **配置项**：填写 `callbackUrl` 并勾选要订阅的事件类型。
* **协议**：支持 **HTTP** 或 **HTTPS**。
* **路径**：平台通过请求头 `X-EVENT-TYPE` 标识事件类型。
* **不可通过 API 动态订阅**。

***

### 请求头说明

平台每次回调都会附带以下 HTTP 头：

| Header 名称         | 描述                 |
| ----------------- | ------------------ |
| `X-EVENT-ID`      | 事件唯一 ID（幂等键）       |
| `X-EVENT-TYPE`    | 事件类型（枚举 EventType） |
| `X-EVENT-VERSION` | 事件版本号              |

***

### 事件结构（JSON）

平台推送的回调请求体为 **JSON**，顶层字段采用 **snake\_case**：

```json
{
  "event_type": "EVENT_BALANCE",
  "event_id": "aabbccdd-1122-3344-5566-77889900",
  "data": { /* 事件对应的数据体 */ }
}
```

#### 事件类型（EventType）

当前有效事件类型：`EVENT_BALANCE`、`EVENT_TRON_MATE_SUBSCRIPTION`、`EVENT_DELEGATION`（如收到未知类型，请记录并忽略）。

<table data-full-width="true"><thead><tr><th>事件类型</th><th>事件名称</th><th>触发场景</th><th>回调数据结构</th><th>说明与处理</th></tr></thead><tbody><tr><td><strong><code>EVENT_BALANCE</code></strong></td><td>余额变动事件（Balance Changed）</td><td>当用户账户余额发生变化（充值、提现、质押收益、消费、能量支出等）</td><td><code>BalanceChangeEventPayload</code>包含字段：<code>payment_hash</code>、<code>balance_type</code>、<code>billing_type</code>、<code>coin_type</code>、<code>amount_sun</code>、<code>balance</code>、<code>balance_usdt</code>、<code>timestamp</code>、<code>remark</code></td><td>用于通知账户余额的实时变化；可据此更新资金记录或展示统计。</td></tr><tr><td><strong><code>EVENT_TRON_MATE_SUBSCRIPTION</code></strong></td><td>波场伴侣订阅事件（Tron Mate Subscription）</td><td>订阅 CatFee Tron Mate 服务（BASIC / PRO 等）成功时触发</td><td><code>TronMateSubscriptionEventPayload</code>包含字段：<code>payment_hash</code>、<code>payment_amount_sun</code>、<code>payment_timestamp</code>、<code>subscribe_type</code>、<code>address</code></td><td>通知有新订阅发生；每个地址每24小时触发一次。</td></tr><tr><td><strong><code>EVENT_DELEGATION</code></strong></td><td>代理确认事件（Delegation Confirmed）</td><td>用户能量(或带宽)代理确认上链后触发</td><td><code>DelegationEventPayload</code>包含字段：<code>order_id</code>、<code>resource_type</code>、<code>delegation_type</code>、<code>receiver</code>、<code>payment_amount_sun</code>、<code>payment_timestamp</code>、<code>duration</code>、<code>quantity</code>、<code>staked_sun</code>、<code>delegation_hash</code>、<code>delegation_timestamp</code></td><td>通知能量(或带宽)代理确认成功，可用于账单记录同步。</td></tr></tbody></table>

#### 示例：余额变动（EVENT\_BALANCE）

```json
{
  "event_type": "EVENT_BALANCE",
  "event_id": "aabbccdd-1122-3344-5566-77889900",
  "data": {
    "balance_type": "BALANCE_CHANGE_TRANSFER",
    "billing_type": "BILLING_ENERGY",
    "coin_type": "USDT",
    "amount_sun": 1000000,
    "balance": 500000000,
    "balance_usdt": 2000000,
    "timestamp": 1760505600,
    "remark": "transfer in"
  }
}
```

#### 示例：订阅（EVENT\_TRON\_MATE\_SUBSCRIPTION）

```json
{
  "event_type": "EVENT_TRON_MATE_SUBSCRIPTION",
  "event_id": "22334455-6677-8899-aabb-ccddeeff",
  "data": {
    "payment_amount_sun": 5000000,
    "payment_timestamp": 1760505600,
    "subscribe_type": "SUBSCRIBE_PRO",
    "address": "TGxxx..."
  }
}
```

***

### 回调请求格式

* **方法**：`POST`
* **Content-Type**：`application/json; charset=utf-8`
* **请求头**：包含 `X-EVENT-ID`、`X-EVENT-TYPE`、`X-EVENT-VERSION`

#### 示例请求

```http
POST /callback HTTP/1.1
Host: example.com
Content-Type: application/json
X-EVENT-ID: aabbccdd-1122-3344-5566-77889900
X-EVENT-TYPE: EVENT_BALANCE
X-EVENT-VERSION: 2025-01-01

{
  "event_type": "EVENT_BALANCE",
  "event_id": "aabbccdd-1122-3344-5566-77889900",
  "data": {
    "balance_type": "BALANCE_CHANGE_TRANSFER",
    "billing_type": "BILLING_ENERGY",
    "coin_type": "USDT",
    "amount_sun": 1000000,
    "balance": 500000000,
    "balance_usdt": 2000000,
    "timestamp": 1760505600,
    "remark": "transfer in"
  }
}
```

### 事件数据定义：

## The BalanceEvent object

```json
{"openapi":"3.1.0","info":{"title":"Notifier API","version":"0.1#@BUILD_ID@"},"components":{"schemas":{"BalanceEvent":{"description":"余额变动事件","properties":{"event_type":{"type":"string","description":"事件类型","enum":["EVENT_UNKNOWN","EVENT_BALANCE","EVENT_TRON_MATE_SUBSCRIPTION","EVENT_DELEGATION","EVENT_ADDRESS_MONITOR","EVENT_EMAIL_MESSAGE","EVENT_TELEGRAM_MESSAGE","EVENT_INTERNAL_MESSAGE","UNRECOGNIZED"]},"event_id":{"type":"string","description":"订单ID，幂等"},"data":{"$ref":"#/components/schemas/BalanceEventData"}}},"BalanceEventData":{"description":"账户变动信息","properties":{"balance_type":{"type":"string","description":"余额类型","enum":["BALANCE_CHANGE_RECHARGE","BALANCE_CHANGE_TRANSFER","BALANCE_CHANGE_DAPP","BALANCE_CHANGE_BALANCE","BALANCE_CHANGE_API","BALANCE_CHANGE_BOT","BALANCE_CHANGE_WITHDRAW","BALANCE_CHANGE_REFUND","UNRECOGNIZED"]},"billing_type":{"type":"string","description":"业务计费类型","enum":["UNKNOWN_BILLING_TYPE","BILLING_ENERGY","BILLING_BANDWIDTH","BILLING_PREMIUM","BILLING_NODE","BILLING_MONITOR","BILLING_DAY_ENERGY","BILLING_MULTIDAY_ENERGY","BILLING_FLASH","BILLING_SUBSCRIBE","BILLING_PERMISSION","BILLING_MARGIN","BILLING_SECURITY","BILLING_SWAP","BILLING_REALTIME","UNRECOGNIZED"]},"coin_type":{"type":"string","description":"币种","enum":["USDT","TRX","ETH","BTC","BNB","BUSD"]},"amount_sun":{"type":"integer","format":"int64","description":"金额sun"},"balance":{"type":"integer","format":"int64","description":"trx余额"},"balance_usdt":{"type":"integer","format":"int64","description":"usdt余额"},"timestamp":{"type":"integer","format":"int64","description":"时间"},"remark":{"type":"string","description":"备注"}}}}}}
```

## The DelegationEvent object

```json
{"openapi":"3.1.0","info":{"title":"Notifier API","version":"0.1#@BUILD_ID@"},"components":{"schemas":{"DelegationEvent":{"properties":{"event_type":{"type":"string","description":"事件类型","enum":["EVENT_UNKNOWN","EVENT_BALANCE","EVENT_TRON_MATE_SUBSCRIPTION","EVENT_DELEGATION","EVENT_ADDRESS_MONITOR","EVENT_EMAIL_MESSAGE","EVENT_TELEGRAM_MESSAGE","EVENT_INTERNAL_MESSAGE","UNRECOGNIZED"]},"event_id":{"type":"string","description":"订单ID，幂等"},"data":{"$ref":"#/components/schemas/DelegationEventData"}}},"DelegationEventData":{"description":"代理详细数据","properties":{"resource_type":{"type":"string","description":"资源类型：ENERGY | BANDWIDTH","enum":["ENERGY","BANDWIDTH","UNRECOGNIZED"]},"delegation_type":{"type":"string","description":"代理类型","enum":["DELEGATION_UNKNOWN","DELEGATION_NORMAL","DELEGATION_MATE_SLOT","DELEGATION_MATE_REPLENISH","UNRECOGNIZED"]},"receiver":{"type":"string","description":"接收地址"},"payment_amount_sun":{"type":"integer","format":"int64","description":"付款金额"},"payment_timestamp":{"type":"integer","format":"int64","description":"付款时间"},"duration":{"type":"integer","format":"int32","description":"周期（分钟）"},"quantity":{"type":"integer","format":"int32","description":"数量"},"staked_sun":{"type":"integer","format":"int64","description":"质押的trx"},"delegation_hash":{"type":"string","description":"代理hash"},"delegation_timestamp":{"type":"integer","format":"int64","description":"代理时间"}}}}}}
```

## The TronMateSubscriptionEvent object

```json
{"openapi":"3.1.0","info":{"title":"Notifier API","version":"0.1#@BUILD_ID@"},"components":{"schemas":{"TronMateSubscriptionEvent":{"description":"波场伴侣订阅事件","properties":{"event_type":{"type":"string","description":"事件类型","enum":["EVENT_UNKNOWN","EVENT_BALANCE","EVENT_TRON_MATE_SUBSCRIPTION","EVENT_DELEGATION","EVENT_ADDRESS_MONITOR","EVENT_EMAIL_MESSAGE","EVENT_TELEGRAM_MESSAGE","EVENT_INTERNAL_MESSAGE","UNRECOGNIZED"]},"event_id":{"type":"string","description":"订单ID，幂等"},"data":{"$ref":"#/components/schemas/TronMateSubscriptionEventData"}}},"TronMateSubscriptionEventData":{"description":"订阅详细数据","properties":{"payment_amount_sun":{"type":"integer","format":"int64","description":"支付金额"},"payment_timestamp":{"type":"integer","format":"int64","description":"支付时间"},"subscribe_type":{"type":"string","description":"订阅类型(BASIC|PRO)","enum":["SUBSCRIBE_UNKNOWN","SUBSCRIBE_BASIC","SUBSCRIBE_PRO","UNRECOGNIZED"]},"address":{"type":"string","description":"地址"}}}}}}
```

## The AddressMonitorEvent object

```json
{"openapi":"3.1.0","info":{"title":"Notifier API","version":"0.1#@BUILD_ID@"},"components":{"schemas":{"AddressMonitorEvent":{"description":"地址监控事件","properties":{"event_type":{"type":"string","description":"事件类型","enum":["EVENT_UNKNOWN","EVENT_BALANCE","EVENT_TRON_MATE_SUBSCRIPTION","EVENT_DELEGATION","EVENT_ADDRESS_MONITOR","EVENT_EMAIL_MESSAGE","EVENT_TELEGRAM_MESSAGE","EVENT_INTERNAL_MESSAGE","UNRECOGNIZED"]},"event_id":{"type":"string","description":"订单ID，幂等"},"data":{"$ref":"#/components/schemas/AddressMonitorEventData"}}},"AddressMonitorEventData":{"description":"地址监控详细数据","properties":{"txn_hash":{"type":"string","description":"交易哈希"},"block_id":{"type":"integer","format":"int64","description":"区块号"},"name":{"type":"string","description":"监控地址昵称"},"address":{"type":"string","description":"监控地址"},"from_address":{"type":"string","description":"转账付款地址"},"contract_address":{"type":"string","description":"合约地址"},"to_address":{"type":"string","description":"转账收款地址"},"amount":{"type":"integer","format":"int64","description":"转账金额"},"decimals":{"type":"integer","format":"int32","description":"转账金额精度"},"currency":{"type":"string","description":"货币类型","enum":["USDT","TRX","ETH","BTC","BNB","BUSD"]},"chain":{"type":"string","description":"区块链类型","enum":["TRON","ETH","BTC","UNRECOGNIZED"]},"paid_at":{"type":"integer","format":"int64","description":"支付时间"}}}}}}
```

***

### 返回要求

> ⚠️ **NOTICE:** Please return success to notify the notification server after successfully receiving the callback request. After the notification server receives success, the notification stops.

* 成功接收后，**必须**返回：

  ```http
  HTTP/1.1 200 OK
  Content-Type: text/plain; charset=utf-8

  ```
* 平台以返回 `200` 为成功，否则将重试。

***

### 重试机制

若响应非 200 ，系统会进行重试。

#### 重试次数与间隔

共 **10 次通知**，间隔如下：

```
0s / 15s / 30s / 3m / 10m / 20m / 30m / 60m / 3h / 6h
```

若连续 10 次均失败，则系统放弃并记录。

***

### 幂等处理建议

* 请使用 `X-EVENT-ID` 作为幂等键。
* 对于已处理事件，应直接返回 `200` 。

***

### 示例代码

#### Node.js (Express)

```js
import express from 'express';
const app = express();
app.use(express.json());
const processed = new Set();

app.post('/callback', (req, res) => {
  const eventId = req.header('X-EVENT-ID');
  if (processed.has(eventId)) return res.status(200).type('text/plain').send('success');
  processed.add(eventId);

  const evt = req.body; // { event_type, data }
  // TODO: 处理事件

  res.status(200).type('text/plain').send('success');
});

app.listen(8080);
```

#### Java (Spring Boot)

```java
@RestController
public class WebhookController {
  private final Set<String> processed = Collections.synchronizedSet(new HashSet<>());

  @PostMapping(value = "/callback", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
  public ResponseEntity<String> handle(@RequestHeader("X-EVENT-ID") String id,
                                       @RequestBody Map<String, Object> evt) {
    synchronized (processed) {
      if (processed.contains(id)) return ResponseEntity.ok("success");
      processed.add(id);
    }

    // TODO: 事件处理逻辑

    return ResponseEntity.ok("success");
  }
}
```

#### Python (FastAPI)

```python
from fastapi import FastAPI, Request, Header, Response
app = FastAPI()
processed = set()

@app.post("/callback")
async def webhook(req: Request, x_event_id: str = Header(None)):
    if x_event_id in processed:
        return Response(content="success", media_type="text/plain")
    processed.add(x_event_id)

    evt = await req.json()
    # TODO: 事件处理

    return Response(content="success", media_type="text/plain")
```

***

### FAQ

**Q1：返回 JSON 可以吗？** 可以。只要返回的HTTP状态为200即可。

**Q2：HTTP 支持吗？** 支持 HTTP 与 HTTPS。

**Q3：可否动态订阅？** 不支持，仅能在用户中心配置。

**Q4：如何分发不同事件？** 根据请求头 `X-EVENT-TYPE` 实现逻辑分支。

**Q5：事件会乱序吗？** 系统尽量保证同类事件顺序，但不作绝对保证。

**Q6：事件版本如何处理？** 根据 `X-EVENT-VERSION` 选择解析方式，未知字段请忽略。

***

**版本：v1.1.1（2025-10-21）**
