# 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_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_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_INTERNAL_MESSAGE","UNRECOGNIZED"]},"event_id":{"type":"string","description":"订单ID，幂等"},"data":{"$ref":"#/components/schemas/BalanceEventData"}}},"BalanceEventData":{"description":"账户变动信息","properties":{"balance_type":{"type":"string","description":"余额类型","enum":["BALANCHE_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","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)**


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.catfee.io/en/getting-started/webhook.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
