---
url: /guide/webhooks.md
---
# 回调（事件）

充值入账、提现状态变化时，我们向你配置的回调地址 `POST` 一条事件。

## 事件格式

```json
{
  "event_id": "evt_1987654321098754",
  "type": "deposit.confirmed",
  "created_at": "1762650000000",
  "data": { "deposit_id": "1987654321098752", "status": "CONFIRMED", "amount": "12.5", "...": "..." }
}
```

`data` 与对应 GET 接口的返回**完全一致**，是事件发生时的快照：

| type | 触发时机 | data 等同于 |
|---|---|---|
| `deposit.confirmed` | 充值达到确认数、已入账 | `GET /v1/deposits/{deposit_id}` |
| `withdraw.broadcasted` | 提现交易已广播，等待链上确认 | `GET /v1/withdraws/{withdraw_id}` |
| `withdraw.confirmed` | 提现已确认（终态）；池内转账直接进入此状态 | 同上 |
| `withdraw.failed` | 提现失败（终态），原因见 `data.reason`；资金池商户创建时冻结的金额已解冻 | 同上 |
| `withdraw.rejected` | 提现审核被拒（终态），原因见 `data.reason`；资金池商户创建时冻结的金额已解冻 | 同上 |

以后可能新增事件类型，**不认识的 type 请直接返回 2xx 忽略**。

## 请求头与验签

| 请求头 | 说明 |
|---|---|
| `X-Webhook-Id` | 等于 `event_id` |
| `X-Timestamp` | 本次投递时间，Unix 毫秒（每次重试都会变） |
| `X-Signature` | `hex(HMAC-SHA256(回调密钥, X-Timestamp + "\n" + body))` |

验签时必须用**收到的原始 body 字节**，不要先解析 JSON 再序列化。建议同时拒绝时间戳与本机相差超过 5 分钟的请求，防止重放。

测试数据：回调密钥 `whsec-test`，`X-Timestamp: 1762650000000`，body 为

```json
{"event_id":"evt_1","type":"deposit.confirmed","created_at":"1762650000000","data":{}}
```

签名应为 `31c194e933a5f62c752bace322b571f8e281edf589c97c5b672ac245e8a54664`。各语言实现见 [多语言示例](./examples)。

## 应答、重试与去重

* 返回任意 **2xx** 视为成功，响应体不限。单次请求超时 30 秒。
* 失败（非 2xx、超时、连不上）时：每轮先即时重试最多 3 次，仍失败则按退避等待下一轮——1 秒、3 秒、10 秒、30 秒、2 分钟、5 分钟、10 分钟、30 分钟、2 小时、6 小时，之后每 12 小时一轮；共 15 轮（约 3 天）仍失败则标记为 `FAILED` 并停止。
* **同一事件可能送达多次**（重试、手动重发），请按 `event_id` 去重，处理逻辑要幂等。
* **事件可能乱序**。以 `data.status` 为准；需要时用 GET 接口查最新状态。
* 请尽快返回 2xx，耗时的业务放到异步队列里处理。

## 漏收补救

* `GET /v1/events?delivery_status=FAILED` 找出没送达的事件，按 `data` 补处理；
* `POST /v1/events/{event_id}/resend` 让我们立即按当前回调地址重新投递一次（`event_id` 不变）；
* 充值没有回调也查不到记录时（例如链上推送遗漏），可用 `POST /v1/deposits/rescan` 按交易哈希补单。
