> 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/en/getting-started/webhook.md).

# Webhook

#### Overview

When an event occurs on the platform, CatFee will send an HTTP `POST` request to your configured callback address (`callbackUrl`).\
You need to:

1. Receive and parse the JSON data.
2. Identify the event type via the `X-EVENT-TYPE` header.
3. Return **HTTP 200** after successful processing.
4. Otherwise, the platform will retry according to its retry policy.

***

#### Configure the Callback URL

* **Location:** Go to **User Center → API Settings**.
* **Configuration:** Fill in `callbackUrl` and check the event types you want to subscribe to.
* **Protocol:** Both **HTTP** and **HTTPS** are supported.
* **Identification:** The event type is indicated by the request header `X-EVENT-TYPE`.
* **Note:** Dynamic subscription via API is **not supported**.

***

#### Request Headers

Each webhook request includes the following HTTP headers:

| Header Name       | Description                       |
| ----------------- | --------------------------------- |
| `X-EVENT-ID`      | Unique event ID (idempotency key) |
| `X-EVENT-TYPE`    | Event type (enum `EventType`)     |
| `X-EVENT-VERSION` | Event version number              |

***

#### Event Structure (JSON)

The request body of a webhook is **JSON**, using **snake\_case** for top-level fields:

```json
{
  "event_type": "EVENT_BALANCE",
  "event_id": "aabbccdd-1122-3344-5566-77889900",
  "data": { /* event-specific payload */ }
}
```

**Event Types (EventType)**

Currently supported types:\
`EVENT_BALANCE`, `EVENT_TRON_MATE_SUBSCRIPTION`, `EVENT_DELEGATION`\
(If you receive an unknown type, record and ignore it.)

**Example: Balance Change (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"
  }
}
```

**Example: Tron Mate Subscription (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..."
  }
}
```

### Event Data Definitions

## 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 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":"备注"}}}}}}
```

***

#### Request Format

* **Method:** `POST`
* **Content-Type:** `application/json; charset=utf-8`
* **Headers:** Must include `X-EVENT-ID`, `X-EVENT-TYPE`, `X-EVENT-VERSION`

**Example Request**

```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"
  }
}
```

***

#### Event Data Definitions

**The `DelegationEvent` object**

Describes TRX delegation details (energy or bandwidth).\
(Full schema as in the OpenAPI JSON definition provided in the original doc.)

**The `BalanceEvent` object**

Describes account balance changes, including recharge, transfer, dApp consumption, API billing, refund, and others.

**The `TronMateSubscriptionEvent` object**

Describes subscription information for Tron Mate, including payment amount, payment time, subscription type (`BASIC` or `PRO`), and address.

***

#### Response Requirements

> ⚠️ **NOTICE:** Please return `success` to inform the notification server that you have successfully received and handled the event. Once the notification server receives a success response, it stops retrying.

* After successful handling, **you must return:**

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

success
```

* The platform treats HTTP 200 as success; otherwise, it will retry.

***

#### Retry Mechanism

If your endpoint does not return status 200, the system retries the notification.

**Retry Schedule**

Up to **10 attempts**, with the following intervals:

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

After 10 consecutive failures, the system stops retrying and logs the failure.

***

#### Idempotency Recommendation

* Use `X-EVENT-ID` as the idempotency key.
* If the event has already been processed, immediately return `200`.

***

#### Example Code

**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: handle event

  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: handle event logic

    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: handle event

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

***

#### FAQ

**Q1: Can I return JSON?**\
Yes. As long as the HTTP status is 200, the response format does not matter.

**Q2: Does HTTP work?**\
Yes, both HTTP and HTTPS are supported.

**Q3: Can I subscribe dynamically?**\
No. Subscriptions can only be configured in the User Center.

**Q4: How to handle different event types?**\
Use the `X-EVENT-TYPE` header to branch logic.

**Q5: Are events ordered?**\
The system tries to preserve order for the same event type, but it’s not guaranteed.

**Q6: How should I handle event versioning?**\
Use the `X-EVENT-VERSION` header to select your parsing logic; ignore unknown fields.

***

**Version: v1.1.1 (2025-10-21)**
