帮助中心Webhook文档 › 处理Webhook请求
WEBHOOK

处理 Webhook 请求

在您的服务器上正确接收、验证和处理翠鸟 Webhook POST 请求

处理器设计原则

高质量的 Webhook 处理器应遵循以下原则:

  • 快速响应:收到请求后立即返回 200,业务处理逻辑放入异步队列,避免超时
  • 幂等设计:同一事件可能因网络问题重复投递,通过事件 ID 去重确保幂等
  • 签名验证:处理任何业务逻辑前先验证签名真实性
  • 未知事件容忍:对于未订阅或未知的事件类型,静默忽略并返回 200

Java(Spring Boot)示例

JAVA
@RestController @RequestMapping("/webhooks") public class CuiniaoWebhookController { @Autowired private WebhookVerifier verifier; @Autowired private AsyncLeadProcessor leadProcessor; @PostMapping(value = "/cuiniao", consumes = "application/json") public ResponseEntity<Void> handle( HttpServletRequest request, @RequestBody byte[] rawBody, @RequestHeader("X-Cuiniao-Signature") String signature, @RequestHeader("X-Cuiniao-Timestamp") String timestamp ) { // 1. 签名验证 if (!verifier.verify(new String(rawBody), signature, timestamp)) { return ResponseEntity.status(400).build(); } // 2. 解析事件 WebhookEvent event = objectMapper.readValue(rawBody, WebhookEvent.class); // 3. 幂等检查(通过 Redis / DB 记录已处理事件ID) if (eventDeduplicator.isProcessed(event.getId())) { return ResponseEntity.ok().build(); } // 4. 异步处理,立即返回 200 switch (event.getType()) { case "lead.created": leadProcessor.onLeadCreated(event.getData()); break; case "lead.status_updated": leadProcessor.onLeadStatusUpdated(event.getData()); break; default: // 未知事件类型:忽略 break; } eventDeduplicator.markProcessed(event.getId()); return ResponseEntity.ok().build(); } }
Spring Boot 默认会将请求体解析为对象,这会导致签名验证失败(因为原始字节已被消费)。确保使用 @RequestBody byte[] 接收原始字节,或配置 HttpMessageConverter 缓存请求体。

Python(FastAPI)示例

PYTHON
from fastapi import FastAPI, Request, HTTPException, BackgroundTasks from cuiniao.webhook import WebhookVerifier import json, os app = FastAPI() verifier = WebhookVerifier(os.environ["CUINIAO_WEBHOOK_SECRET"]) @app.post("/webhooks/cuiniao") async def handle_webhook( request: Request, background_tasks: BackgroundTasks ): raw_body = await request.body() signature = request.headers.get("X-Cuiniao-Signature", "") timestamp = request.headers.get("X-Cuiniao-Timestamp", "") # 签名验证 if not verifier.verify(raw_body, signature, timestamp): raise HTTPException(status_code=400, detail="Invalid signature") event = json.loads(raw_body) # 异步处理,立即返回 200 background_tasks.add_task(process_event, event) return {"received": True} async def process_event(event: dict): event_type = event.get("type") if event_type == "lead.created": lead = event["data"] # 处理新线索逻辑... await notify_sales_team(lead) elif event_type == "lead.status_updated": # 同步到 CRM... pass

Node.js(Express)示例

JAVASCRIPT
const express = require('express'); const { WebhookVerifier } = require('@cuiniao/sdk'); const app = express(); const verifier = new WebhookVerifier(process.env.CUINIAO_WEBHOOK_SECRET); // 必须使用 express.raw() 保留原始请求体用于签名验证 app.post('/webhooks/cuiniao', express.raw({ type: 'application/json' }), async (req, res) => { const sig = req.headers['x-cuiniao-signature']; const ts = req.headers['x-cuiniao-timestamp']; if (!verifier.verify(req.body, sig, ts)) { return res.status(400).json({ error: 'Invalid signature' }); } const event = JSON.parse(req.body); // 立即返回 200,异步处理 res.status(200).json({ received: true }); setImmediate(async () => { try { await processEvent(event); } catch (err) { console.error('Webhook processing error:', err); } }); }); async function processEvent(event) { switch (event.type) { case 'lead.created': await createLeadInCRM(event.data); await sendSlackNotification(event.data); break; case 'resource.published': await updateContentIndex(event.data); break; } }