Files
jczxw-pc/public/docs/webhook.md
2026-04-23 16:30:57 +08:00

285 lines
6.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Webhook 事件接入
> 订阅业务事件处理订单支付、AI 任务完成等异步通知。
## 🔔 什么是 Webhook
Webhook 是一种「反向 API 调用」机制:
- 传统 API你 → 平台(主动拉取)
- Webhook平台 → 你(被动接收)
当某个事件发生时(如支付成功),平台主动通知你的服务器。
## 📋 可订阅的事件
| 事件类型 | 说明 |
|----------|------|
| `payment.completed` | 支付完成 |
| `payment.failed` | 支付失败 |
| `subscription.created` | 订阅创建 |
| `subscription.cancelled` | 订阅取消 |
| `ai.task.completed` | AI 任务完成 |
| `ai.task.failed` | AI 任务失败 |
| `file.uploaded` | 文件上传完成 |
| `project.created` | 项目创建 |
| `user.invited` | 用户被邀请 |
| `workflow.triggered` | 工作流触发 |
## 🚀 配置 Webhook
### 步骤 1创建 Webhook 端点
在你的服务器创建一个 API 端点:
```typescript
// Node.js Express 示例
app.post('/webhook/websopy', express.raw({ type: 'application/json' }), (req, res) => {
// 验证签名
const signature = req.headers['x-websopy-signature']
const timestamp = req.headers['x-websopy-timestamp']
if (!verifySignature(req.body, signature, timestamp)) {
return res.status(401).send('Invalid signature')
}
// 处理事件
const event = JSON.parse(req.body)
console.log('收到事件:', event.type)
// 立即返回 200重要
res.status(200).send('OK')
// 异步处理业务逻辑
processEvent(event)
})
```
### 步骤 2在控制台配置
1. 进入 **开发者中心 → Webhook**
2. 点击 **添加端点**
3. 填写配置:
| 配置项 | 说明 |
|--------|------|
| URL | 你的服务器地址,如 `https://your-server.com/webhook/websopy` |
| 事件类型 | 选择要订阅的事件 |
| 密钥 | 用于验证签名的密钥 |
### 步骤 3验证签名
```typescript
import crypto from 'crypto'
function verifySignature(body, signature, timestamp) {
const secret = process.env.WEBSOPY_WEBHOOK_SECRET
// 检查时间戳5分钟内有效
const ts = parseInt(timestamp)
if (Date.now() - ts > 5 * 60 * 1000) {
return false
}
// 计算签名
const payload = `${timestamp}.${body}`
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex')
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
)
}
```
## 📨 事件数据格式
### 通用结构
```json
{
"id": "evt_abc123xyz",
"type": "payment.completed",
"created_at": "2024-01-15T10:30:00Z",
"data": {
// 事件相关数据
}
}
```
### 支付完成事件
```json
{
"id": "evt_abc123xyz",
"type": "payment.completed",
"created_at": "2024-01-15T10:30:00Z",
"data": {
"order_id": "ord_xyz789",
"amount": 29900,
"currency": "CNY",
"payment_method": "alipay",
"status": "completed",
"user_id": "user_123"
}
}
```
### AI 任务完成事件
```json
{
"id": "evt_ai123",
"type": "ai.task.completed",
"created_at": "2024-01-15T10:30:00Z",
"data": {
"task_id": "task_abc",
"task_type": "ai_chat",
"result": {
"content": "这里是 AI 的回复...",
"model": "gpt-4",
"usage": {
"prompt_tokens": 50,
"completion_tokens": 120
}
},
"user_id": "user_123"
}
}
```
## 🔧 处理业务逻辑
### 处理支付事件
```typescript
async function processPaymentEvent(event) {
const { order_id, amount, status } = event.data
switch (status) {
case 'completed':
// 1. 更新订单状态
await updateOrderStatus(order_id, 'paid')
// 2. 发放权益
await grantPremiumAccess(order_id)
// 3. 发送通知
await sendConfirmationEmail(order_id)
break
case 'failed':
// 处理失败
await handlePaymentFailure(order_id)
break
}
}
```
### 处理 AI 任务事件
```typescript
async function processAITaskEvent(event) {
const { task_id, result, user_id } = event.data
// 保存 AI 回复
await saveChatMessage({
taskId: task_id,
userId: user_id,
content: result.content,
model: result.model,
tokens: result.usage.completion_tokens
})
// 更新用户用量
await updateUserUsage(user_id, result.usage)
}
```
## ⏰ 失败重试
如果你的服务器返回非 200 状态码Websopy 会自动重试:
| 重试次数 | 延迟 |
|----------|------|
| 第 1 次 | 1 分钟 |
| 第 2 次 | 5 分钟 |
| 第 3 次 | 30 分钟 |
| 第 4 次 | 2 小时 |
| 第 5 次 | 12 小时 |
超过重试次数仍未成功,事件将被标记为失败,可在控制台手动重试。
### 幂等处理
```typescript
const processedEvents = new Set()
app.post('/webhook/websopy', async (req, res) => {
const event = JSON.parse(req.body)
// 幂等检查
if (processedEvents.has(event.id)) {
return res.status(200).send('Already processed')
}
try {
await processEvent(event)
processedEvents.add(event.id)
res.status(200).send('OK')
} catch (error) {
// 返回错误,触发重试
res.status(500).send('Error')
}
})
```
## 🧪 本地开发测试
### 使用 ngrok
```bash
# 安装 ngrok
npm install -g ngrok
# 启动本地服务
ngrok http 3000
# 复制输出的 https URL 到 Webhook 配置
# https://abc123.ngrok.io/webhook/websopy
```
### Webhook 测试工具
控制台提供「发送测试事件」功能,可以手动触发任意事件类型进行测试。
## ❓ 常见问题
### Q: 收到重复事件怎么办?
使用事件 ID 做幂等检查,参考上文示例。
### Q: 处理超时怎么办?
```typescript
app.post('/webhook', async (req, res) => {
// 立即返回
res.status(200).send('OK')
// 异步处理
setImmediate(() => processEvent(req.body)) // 使用 setImmediate 不阻塞
})
```
### Q: 如何查看 webhook 日志?
在控制台 → Webhook → 查看每个端点的请求历史和响应状态。
---
**上一步:** [流式输出SSE接入](./streaming.md)
**下一步:** [REST API 完整参考](./api-reference.md)