> ## Documentation Index
> Fetch the complete documentation index at: https://docs.killb.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> 来自 KillB 的实时事件通知

## 什么是 Webhooks？

**Webhooks** 是 KillB 在事件发生时发送到您服务器的 HTTP 回调。它们能够实时通知 ramps、用户、账户和交易，而无需持续轮询。

<Info>
  Webhooks 是监控交易状态并保持应用程序与 KillB 同步的推荐方式。
</Info>

## Webhooks 如何工作

```mermaid theme={null}
sequenceDiagram
    participant KillB
    participant Your Server
    participant Your App
    
    KillB->>KillB: Ramp 状态更改
    KillB->>Your Server: POST webhook 事件
    Your Server->>Your Server: 验证签名
    Your Server->>Your Server: 处理事件
    Your Server-->>KillB: 200 OK
    Your Server->>Your App: 更新 UI
```

## Webhook 事件结构

所有 webhook 事件都遵循此结构：

```typescript theme={null}
{
  id: string,              // UUID - 唯一事件 ID
  event: string,           // 事件类型：RAMP | USER | TRANSACTION | ACCOUNT | CUSTODIAL_ACCOUNT
  action: string,          // 操作：CREATE | UPDATE | DELETE
  data: object,            // 事件特定数据
  createdAt: string,       // ISO 8601 时间戳
  updatedAt: string,       // ISO 8601 时间戳
  attempts: number         // 交付尝试次数（0 到 5）
}
```

## Webhook 事件

KillB 支持以下事件类型：

<Tabs>
  <Tab title="RAMP">
    **Ramp 交易事件**

    **事件类型：** `RAMP`

    **操作：**

    * `CREATE` - 创建新 ramp
    * `UPDATE` - Ramp 状态已更改
    * `DELETE` - Ramp 已取消（罕见）

    **常见状态值：**

    * `QUOTE_CREATED` - 报价已创建
    * `PENDING_PAYMENT` - 等待支付
    * `CASH_IN_PROCESSING` - 正在处理支付
    * `CASH_IN_COMPLETED` - 支付已确认
    * `PROCESSING` - 正在转换货币
    * `CASH_OUT_PROCESSING` - 正在发送资金
    * `COMPLETED` - Ramp 已完成
    * `FAILED` - Ramp 失败
    * `CANCELED` - Ramp 已取消
  </Tab>

  <Tab title="USER">
    **用户和 KYC 事件**

    **事件类型：** `USER`

    **操作：**

    * `CREATE` - 新用户注册
    * `UPDATE` - 用户信息或 KYC 状态已更改
    * `DELETE` - 用户已删除（罕见）

    **状态值：**

    * `PENDING` - 等待验证
    * `ACTIVE` - 已验证并激活
    * `REJECTED` - KYC 被拒绝
    * `SUSPENDED` - 账户已暂停

    **访问级别：**

    * `L0` - 未验证
    * `L1` - 基本验证
    * `L2` - 标准验证
    * `L3` - 增强验证
    * `L4` - 高级验证
  </Tab>

  <Tab title="ACCOUNT">
    **账户验证事件**

    **事件类型：** `ACCOUNT`

    **操作：**

    * `CREATE` - 添加新账户
    * `UPDATE` - 账户状态或详情已更改
    * `DELETE` - 账户已删除

    **账户类型：**

    * `PSE` - 哥伦比亚银行账户
    * `SPEI` - 墨西哥银行账户
    * `ACH` - 美国银行账户
    * `WIRE` - 电汇账户
    * `WALLET` - 加密货币钱包
    * `PIX` - 巴西 PIX 账户
    * `TRANSFIYA` - 哥伦比亚移动钱包

    **状态值：**

    * `PENDING` - 等待验证
    * `ACTIVE` - 已验证并激活
    * `REJECTED` - 验证失败
    * `INACTIVE` - 已禁用
  </Tab>

  <Tab title="TRANSACTION">
    **储蓄账户交易**

    **事件类型：** `TRANSACTION`

    **操作：**

    * `CREATE` - 启动新交易
    * `UPDATE` - 交易状态已更改
    * `DELETE` - 交易已取消（罕见）

    **交易类型：**

    * `DEPOSIT` - 储蓄存款
    * `WITHDRAWAL` - 储蓄提款
    * `INTEREST` - 利息支付
    * `FEE` - 费用收取
  </Tab>

  <Tab title="CUSTODIAL_ACCOUNT">
    **托管账户事件**

    **事件类型：** `CUSTODIAL_ACCOUNT`

    **操作：**

    * `CREATE` - 创建托管账户
    * `UPDATE` - 余额或状态已更新
    * `DELETE` - 账户已关闭（罕见）

    **更改原因：**

    * `DEPOSIT` - 已存入资金
    * `WITHDRAWAL` - 已提取资金
    * `INTEREST` - 已记入利息
    * `FEE` - 已收取费用
    * `ADJUSTMENT` - 手动调整
  </Tab>
</Tabs>

## 设置 Webhooks

### 创建 Webhook 配置

```bash theme={null}
POST /api/v2/webhooks
```

**请求：**

```json theme={null}
{
  "url": "https://api.yourapp.com/webhooks/killb",
  "secret": "your-secret-key-min-32-chars",
  "events": ["RAMP", "USER", "ACCOUNT"]
}
```

**响应：**

```json theme={null}
{
  "id": "webhook-id",
  "customerId": "customer-id",
  "url": "https://api.yourapp.com/webhooks/killb",
  "events": ["RAMP", "USER", "ACCOUNT"],
  "active": true,
  "createdAt": "2024-01-15T10:30:00.000Z"
}
```

<CodeGroup>
  ```javascript Node.js theme={null}
  const setupWebhook = async () => {
    const response = await fetch('https://sandbox.killb.app/api/v2/webhooks', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        url: 'https://api.yourapp.com/webhooks/killb',
        secret: process.env.WEBHOOK_SECRET,
        events: ['RAMP', 'USER', 'ACCOUNT', 'TRANSACTION']
      })
    });
    
    return await response.json();
  };
  ```

  ```python Python theme={null}
  def setup_webhook(api_key, access_token, webhook_url, secret):
      response = requests.post(
          'https://sandbox.killb.app/api/v2/webhooks',
          headers={
              'Authorization': f'Bearer {access_token}',
              'Content-Type': 'application/json'
          },
          json={
              'url': webhook_url,
              'secret': secret,
              'events': ['RAMP', 'USER', 'ACCOUNT', 'TRANSACTION']
          }
      )
      return response.json()
  ```
</CodeGroup>

## Webhook 安全

### 签名验证

KillB 使用 HMAC SHA-256 对所有 webhook 请求进行签名。**始终验证签名**以确保真实性。

**标头：** `x-signature-sha256`

<CodeGroup>
  ```javascript Node.js (Express) theme={null}
  const crypto = require('crypto');
  const express = require('express');

  app.post('/webhooks/killb', express.raw({type: 'application/json'}), (req, res) => {
    const signature = req.headers['x-signature-sha256'];
    const payload = req.body.toString();
    
    // 计算预期签名
    const expectedSignature = crypto
      .createHmac('sha256', process.env.WEBHOOK_SECRET)
      .update(payload)
      .digest('hex');
    
    // 验证签名
    if (signature !== expectedSignature) {
      console.error('Invalid webhook signature');
      return res.status(401).json({ error: 'Invalid signature' });
    }
    
    // 解析并处理事件
    const event = JSON.parse(payload);
    processWebhookEvent(event);
    
    res.status(200).json({ received: true });
  });
  ```

  ```python FastAPI theme={null}
  import hmac
  import hashlib
  from fastapi import FastAPI, Header, HTTPException, Request

  app = FastAPI()

  @app.post("/webhooks/killb")
  async def webhook_handler(
      request: Request,
      x_webhook_signature: str = Header(None)
  ):
      # 获取原始正文
      body = await request.body()
      
      # 计算预期签名
      expected_signature = hmac.new(
          WEBHOOK_SECRET.encode(),
          body,
          hashlib.sha256
      ).hexdigest()
      
      # 验证签名
      if not hmac.compare_digest(x_webhook_signature or '', expected_signature):
          raise HTTPException(status_code=401, detail="Invalid signature")
      
      # 解析并处理
      event = await request.json()
      process_webhook_event(event)
      
      return {"received": True}
  ```

  ```php PHP theme={null}
  <?php
  function verifyWebhookSignature($payload, $signature, $secret) {
      $expectedSignature = hash_hmac('sha256', $payload, $secret);
      return hash_equals($expectedSignature, $signature);
  }

  // Webhook 端点
  $payload = file_get_contents('php://input');
  $signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';

  if (!verifyWebhookSignature($payload, $signature, WEBHOOK_SECRET)) {
      http_response_code(401);
      die(json_encode(['error' => 'Invalid signature']));
  }

  $event = json_decode($payload, true);
  processWebhookEvent($event);

  http_response_code(200);
  echo json_encode(['received' => true]);
  ?>
  ```
</CodeGroup>

<Warning>
  **永远不要跳过签名验证！** 未验证的 webhooks 可能被攻击者伪造。
</Warning>

## Webhook 要求

您的 webhook 端点必须：

<AccordionGroup>
  <Accordion title="快速响应" icon="bolt">
    * 在 5 秒内返回 `200 OK`
    * 异步处理事件
    * 不要等待慢速操作
    * 对繁重处理使用作业队列
  </Accordion>

  <Accordion title="处理重试" icon="rotate">
    * 幂等（处理重复事件）
    * 使用事件 ID 检测重复
    * 存储已处理的事件 ID
    * 跳过已处理的事件
  </Accordion>

  <Accordion title="保持可用" icon="signal">
    * 保持 >99% 正常运行时间
    * 仅使用 HTTPS
    * 拥有有效的 SSL 证书
    * 使用适当的状态代码响应
  </Accordion>

  <Accordion title="验证签名" icon="shield">
    * 始终检查 `x-signature-sha256`
    * 使用恒定时间比较
    * 拒绝无效签名
    * 记录验证失败
  </Accordion>
</AccordionGroup>

## 重试逻辑

KillB 使用指数退避自动重试失败的 webhook 交付：

**重试计划：**

* 尝试 0：立即交付
* 尝试 1：1 分钟后
* 尝试 2：5 分钟后
* 尝试 3：15 分钟后
* 尝试 4：1 小时后
* 尝试 5：6 小时后（最终尝试）

**失败标准：**

* 非 2xx 响应代码
* 连接超时（> 5 秒）
* 网络错误

webhook 有效负载中的 `attempts` 字段指示当前交付尝试（0 到 5）。

```javascript theme={null}
// 检查重试次数
if (webhookEvent.attempts > 3) {
  console.warn(`High retry count: ${webhookEvent.attempts}`);
  // 提醒监控系统
}
```

<Tip>
  尝试 5 次失败后，webhook 被标记为永久失败，需要手动干预。检查您的 webhook 日志并修复任何问题。
</Tip>

## 测试 Webhooks

### 使用 ngrok 进行本地测试

```bash theme={null}
# 安装 ngrok
npm install -g ngrok

# 启动本地服务器
node server.js

# 暴露到互联网
ngrok http 3000

# 在 webhook 配置中使用 ngrok URL
# https://abc123.ngrok.io/webhooks/killb
```

### 手动测试

在沙盒中触发测试事件：

```javascript theme={null}
// 创建测试 ramp
const ramp = await createRamp(quoteId, userId, accountId);

// 模拟完成（触发 webhooks）
await fetch('/api/v2/faker/cash-in', {
  method: 'POST',
  headers: { 'Authorization': `Bearer ${token}` },
  body: JSON.stringify({ rampId: ramp.id })
});

// 您的 webhook 应该收到 ramp.cash_in_completed 事件
```

## 管理 Webhooks

### 获取 Webhook 配置

```bash theme={null}
GET /api/v2/webhooks
```

返回您当前的 webhook 配置。

### 更新 Webhook

```bash theme={null}
PATCH /api/v2/webhooks
```

**更新：**

* URL
* Secret
* 要订阅的事件
* 活动状态

```json theme={null}
{
  "url": "https://api.yourapp.com/webhooks/killb-v2",
  "active": true,
  "events": ["RAMP", "USER"]
}
```

### 删除 Webhook

```bash theme={null}
DELETE /api/v2/webhooks
```

删除 webhook 配置。将不再发送事件。

## 事件处理

### 示例处理程序

```javascript theme={null}
const processWebhookEvent = (webhookEvent) => {
  const { id, event, action, data, attempts } = webhookEvent;
  
  console.log(`Processing ${event}.${action} (attempt ${attempts})`);
  
  // 根据事件类型和操作路由
  switch(event) {
    case 'RAMP':
      handleRampEvent(action, data);
      break;
      
    case 'USER':
      handleUserEvent(action, data);
      break;
      
    case 'ACCOUNT':
      handleAccountEvent(action, data);
      break;
      
    case 'TRANSACTION':
      handleTransactionEvent(action, data);
      break;
      
    case 'CUSTODIAL_ACCOUNT':
      handleCustodialEvent(action, data);
      break;
      
    default:
      console.log(`Unhandled event type: ${event}`);
  }
};

const handleRampEvent = async (action, data) => {
  if (action === 'UPDATE' && data.status === 'COMPLETED') {
    // 更新数据库
    await db.ramps.update(data.id, {
      status: data.status,
      updatedAt: data.updatedAt,
      transferProof: data.transferProof
    });
    
    // 通知用户
    await sendEmail(data.userId, 'Transaction Completed', {
      amount: data.toAmount,
      currency: data.toCurrency,
      txHash: data.transferProof
    });
    
    // 更新内部系统
    await updateAccounting(data);
  }
};

const handleUserEvent = async (action, data) => {
  if (action === 'UPDATE') {
    // 检查 KYC 级别是否更改
    await db.users.update(data.id, {
      status: data.status,
      accessLevel: data.accessLevel,
      updatedAt: data.updatedAt
    });
    
    // 如果 KYC 已批准，启用功能
    if (data.status === 'ACTIVE' && data.accessLevel !== 'L0') {
      await enableUserFeatures(data.id, data.accessLevel);
    }
  }
};

const handleAccountEvent = async (action, data) => {
  if (action === 'UPDATE' && data.status === 'ACTIVE') {
    // 账户已验证
    await db.accounts.update(data.id, {
      status: data.status,
      verified: true
    });
    
    // 通知用户他们的账户已准备就绪
    await sendNotification(data.userId, 'Account Verified');
  }
};
```

## 幂等性

使用事件 ID 处理重复的 webhook 交付：

```javascript theme={null}
const handleWebhook = async (webhookEvent) => {
  const { id, event, action, data, attempts } = webhookEvent;
  
  // 检查是否已处理（使用唯一事件 ID）
  const existing = await db.webhookEvents.findOne({ eventId: id });
  
  if (existing) {
    console.log(`Duplicate event ${id}, skipping`);
    return { received: true, duplicate: true };
  }
  
  // 立即存储事件以防止重复处理
  await db.webhookEvents.create({
    eventId: id,
    eventType: event,
    action: action,
    payload: data,
    attempts: attempts,
    receivedAt: new Date(),
    processed: false
  });
  
  try {
    // 处理事件
    await processWebhookEvent(webhookEvent);
    
    // 标记为已处理
    await db.webhookEvents.update(
      { eventId: id },
      { processed: true, processedAt: new Date() }
    );
    
    return { received: true };
  } catch (error) {
    // 记录错误但仍标记为已接收
    await db.webhookEvents.update(
      { eventId: id },
      { error: error.message, failedAt: new Date() }
    );
    
    throw error;
  }
};
```

## 最佳实践

<AccordionGroup>
  <Accordion title="快速确认" icon="bolt">
    ```javascript theme={null}
    app.post('/webhooks/killb', async (req, res) => {
      // 验证签名
      verifySignature(req);
      
      // 立即确认
      res.status(200).json({ received: true });
      
      // 异步处理
      processAsync(req.body);
    });
    ```

    * 立即返回 200 OK
    * 在后台处理事件
    * 使用作业队列（Bull、Celery 等）
    * 不要等待外部服务
  </Accordion>

  <Accordion title="优雅处理失败" icon="shield">
    ```javascript theme={null}
    const processWebhook = async (event) => {
      try {
        await processEvent(event);
      } catch (error) {
        console.error('Webhook processing failed:', error);
        // 记录到监控服务
        await logError(error, event);
        // 不要抛出 - 我们已经处理了
        // KillB 无论如何都会重试
      }
    };
    ```

    * 捕获所有错误
    * 记录失败
    * 确认后不要抛出
    * 监控错误率
  </Accordion>

  <Accordion title="存储事件历史" icon="database">
    ```javascript theme={null}
    await db.webhookLogs.create({
      eventId: event.id,
      eventType: event.event,
      payload: event.data,
      receivedAt: new Date(),
      processed: true
    });
    ```

    * 保留审计跟踪
    * 如果需要，启用重放
    * 轻松调试问题
    * 满足合规要求
  </Accordion>

  <Accordion title="监控 Webhook 健康" icon="heart-pulse">
    * 跟踪交付成功率
    * 多次失败时提醒
    * 监控处理时间
    * 设置正常运行时间监控
    * 检查签名验证失败
  </Accordion>
</AccordionGroup>

## Webhook 安全清单

<Steps>
  <Step title="仅使用 HTTPS">
    永远不要对 webhook 端点使用 HTTP

    ```
    ✅ https://api.yourapp.com/webhooks
    ❌ http://api.yourapp.com/webhooks
    ```
  </Step>

  <Step title="验证每个请求">
    在每个 webhook 上检查 `x-signature-sha256`
  </Step>

  <Step title="使用强密钥">
    最少 32 个字符，随机，安全存储

    ```javascript theme={null}
    const secret = crypto.randomBytes(32).toString('hex');
    ```
  </Step>

  <Step title="实现速率限制">
    防止 webhook 泛滥
  </Step>

  <Step title="记录可疑活动">
    跟踪无效签名和异常模式
  </Step>
</Steps>

## 故障排除

<AccordionGroup>
  <Accordion title="未收到 Webhooks">
    **检查：**

    * Webhook URL 可公开访问
    * SSL 证书有效
    * 防火墙允许 KillB IP
    * 端点快速返回 200 OK
    * 没有反向代理问题

    **测试：**

    ```bash theme={null}
    curl -X POST https://your-webhook-url \
      -H "Content-Type: application/json" \
      -d '{"test": "data"}'
    ```
  </Accordion>

  <Accordion title="签名验证失败">
    **常见问题：**

    * 使用错误的密钥
    * 在验证之前解析正文
    * 字符集/编码问题
    * 空白修改

    **解决方案：**

    ```javascript theme={null}
    // 使用原始正文
    app.use('/webhooks', express.raw({type: 'application/json'}));

    // 在解析之前验证
    verifySignature(req.body.toString(), signature);

    // 然后解析
    const event = JSON.parse(req.body);
    ```
  </Accordion>

  <Accordion title="重复事件">
    **这是正常的！** Webhooks 可能会多次交付。

    **使用以下方式处理：**

    * 事件 ID 跟踪
    * 数据库唯一约束
    * 幂等性密钥
    * "已处理"标志
  </Accordion>
</AccordionGroup>

## 高级配置

### 过滤事件

仅订阅您需要的事件：

```json theme={null}
{
  "url": "https://api.yourapp.com/webhooks/killb",
  "secret": "your-secret",
  "events": ["RAMP"]
}
```

### 多个 Webhooks

为不同的事件类型创建不同的端点：

```javascript theme={null}
// Ramp 事件 → 支付处理器
await createWebhook({
  url: 'https://payments.yourapp.com/webhooks',
  events: ['RAMP']
});

// 用户事件 → CRM
await createWebhook({
  url: 'https://crm.yourapp.com/webhooks',
  events: ['USER']
});
```

<Note>
  每个客户只能有一个活动的 webhook 配置。使用单个端点并在内部路由。
</Note>

## Webhook 监控

```javascript theme={null}
// 跟踪 webhook 健康
const webhookMetrics = {
  received: 0,
  processed: 0,
  failed: 0,
  averageProcessingTime: 0
};

app.post('/webhooks/killb', async (req, res) => {
  const startTime = Date.now();
  webhookMetrics.received++;
  
  try {
    await processWebhook(req.body);
    webhookMetrics.processed++;
  } catch (error) {
    webhookMetrics.failed++;
    throw error;
  } finally {
    const duration = Date.now() - startTime;
    webhookMetrics.averageProcessingTime = 
      (webhookMetrics.averageProcessingTime + duration) / 2;
  }
  
  res.status(200).json({ received: true });
});
```

## 下一步

<CardGroup cols={2}>
  <Card title="Webhook 设置指南" icon="wrench" href="/zh/guides/webhooks/setup">
    分步 webhook 实现
  </Card>

  <Card title="事件参考" icon="list" href="/zh/guides/webhooks/events">
    Webhook 事件的完整列表
  </Card>

  <Card title="安全指南" icon="lock" href="/zh/guides/webhooks/security">
    保护您的 webhook 端点
  </Card>

  <Card title="API 参考" icon="code" href="/api-reference/endpoint/webhooks-create">
    查看 Webhooks API 文档
  </Card>
</CardGroup>
