初始化2
This commit is contained in:
284
public/docs/webhook.md
Normal file
284
public/docs/webhook.md
Normal file
@@ -0,0 +1,284 @@
|
||||
# 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)
|
||||
Reference in New Issue
Block a user