> ## 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.

# Webhook 设置

> 实现和配置 webhook 端点

## 分步设置

<Steps>
  <Step title="创建端点">
    构建 POST 端点以接收 webhooks

    ```javascript theme={null}
    app.post('/webhooks/killb', async (req, res) => {
      // 处理 webhook
      res.status(200).json({ received: true });
    });
    ```
  </Step>

  <Step title="验证签名">
    实现 HMAC 验证

    ```javascript theme={null}
    import { createHash } from 'node:crypto';

    const verifySignature = (payload, expectedSignature, secret) => {
      const signature = createHash('sha256')
        .update(`${JSON.stringify(req.body)}_${my_secret}`)
        .digest('hex');

      return signature === expectedSignature;
    }
    ```
  </Step>

  <Step title="注册 Webhook">
    在 KillB 中配置

    ```bash theme={null}
    POST /api/v2/webhooks
    {
      "url": "https://api.yourapp.com/webhooks/killb",
      "secret": "your-secret-key",
      "events": ["RAMP", "USER"]
    }
    ```
  </Step>

  <Step title="测试">
    创建测试 ramp 并验证 webhook 接收
  </Step>
</Steps>

## 完整实现

<CodeGroup>
  ```javascript Express.js theme={null}
  import express from 'express';
  import { createHash } from 'node:crypto';

  const app = express();

  app.use(express.json());

  app.post('/webhooks/killb', async (req, res) => {
    try {
      // 1. 验证签名
      const signature = req.headers['x-signature-sha256'];
      
      const expectedSignature = createHash('sha256')
        .update(`${JSON.stringify(req.body)}_${process.env.WEBHOOK_SECRET}`)
        .digest('hex');
      
      if (signature !== expectedSignature) {
        console.error('Invalid signature');
        return res.status(401).json({ error: 'Invalid signature' });
      }
      
      // 2. 立即确认
      res.status(200).json({ received: true });
      
      // 3. 异步处理
      processWebhookAsync(req.body).catch(console.error);
      
    } catch (error) {
      console.error('Webhook error:', error);
      res.status(500).json({ error: 'Processing failed' });
    }
  });

  const processWebhookAsync = async (event) => {
    console.log('Processing event:', event.event);
    
    switch(event.event) {
      case 'ramp.completed':
        await handleRampCompleted(event.data);
        break;
      case 'ramp.failed':
        await handleRampFailed(event.data);
        break;
      case 'user.kyc_approved':
        await handleKYCApproved(event.data);
        break;
    }
  };

  app.listen(3000);
  ```

  ```python FastAPI theme={null}
  from fastapi import FastAPI, Header, HTTPException, Request, BackgroundTasks
  import hashlib
  import json
  from typing import Optional

  app = FastAPI()

  @app.post("/webhooks/killb")
  async def webhook_handler(
      request: Request,
      background_tasks: BackgroundTasks,
      x_webhook_signature: Optional[str] = Header(None)
  ):
      # 获取正文为 JSON
      event = await request.json()
      
      # 验证签名
      payload_string = f"{json.dumps(event, separators=(',', ':'))}_{WEBHOOK_SECRET}"
      expected_signature = hashlib.sha256(payload_string.encode()).hexdigest()
      
      if x_webhook_signature != expected_signature:
          raise HTTPException(status_code=401, detail="Invalid signature")
      
      # 立即确认
      background_tasks.add_task(process_webhook, event)
      
      return {"received": True}

  async def process_webhook(event):
      event_type = event.get('event')
      data = event.get('data')
      
      if event_type == 'ramp.completed':
          await handle_ramp_completed(data)
      elif event_type == 'ramp.failed':
          await handle_ramp_failed(data)
  ```

  ```typescript AdonisJS theme={null}
  import { HttpContext } from '@adonisjs/core/http'
  import { createHash } from 'node:crypto'
  import env from '#start/env'
  import emitter from '@adonisjs/core/services/emitter'

  export default class WebhooksController {
    async handleKillB({ request, response }: HttpContext) {
      try {
        // 1. 验证签名
        const signature = request.header('x-signature-sha256')
        const body = request.body()
        
        const expectedSignature = createHash('sha256')
          .update(`${JSON.stringify(body)}_${env.get('WEBHOOK_SECRET')}`)
          .digest('hex')
        
        if (signature !== expectedSignature) {
          return response.unauthorized({ error: 'Invalid signature' })
        }
        
        // 2. 立即确认
        response.ok({ received: true })
        
        // 3. 使用事件发射器异步处理
        emitter.emit('webhook:received', body)
        
      } catch (error) {
        console.error('Webhook error:', error)
        return response.internalServerError({ error: 'Processing failed' })
      }
    }
  }

  // app/listeners/webhook_listener.ts
  import emitter from '@adonisjs/core/services/emitter'

  emitter.on('webhook:received', async (event) => {
    console.log('Processing event:', event.event)
    
    switch(event.event) {
      case 'ramp.completed':
        await handleRampCompleted(event.data)
        break
      case 'ramp.failed':
        await handleRampFailed(event.data)
        break
      case 'user.kyc_approved':
        await handleKYCApproved(event.data)
        break
    }
  })

  // start/routes.ts
  import router from '@adonisjs/core/services/router'
  const WebhooksController = () => import('#controllers/webhooks_controller')

  router.post('/webhooks/killb', [WebhooksController, 'handleKillB'])
  ```
</CodeGroup>

## 注册 Webhook

```bash theme={null}
curl --request POST \
  --url https://sandbox.killb.app/api/v2/webhooks \
  --header 'Authorization: Bearer YOUR_ACCESS_TOKEN' \
  --header 'Content-Type: application/json' \
  --data '{
    "url": "https://api.yourapp.com/webhooks/killb",
    "secret": "your-secret-key-at-least-32-chars",
    "events": ["RAMP", "USER", "ACCOUNT"]
  }'
```

## 本地测试

使用 ngrok 进行本地测试：

```bash theme={null}
# 启动本地服务器
npm start

# 通过 ngrok 暴露
ngrok http 3000

# 使用 ngrok URL
# https://abc123.ngrok.io/webhooks/killb
```

## 下一步

<CardGroup cols={2}>
  <Card title="事件参考" icon="list" href="/zh/guides/webhooks/events">
    所有 webhook 事件类型
  </Card>

  <Card title="安全" icon="shield" href="/zh/guides/webhooks/security">
    保护您的 webhooks
  </Card>
</CardGroup>
