文档
Webhooks API
SmartCV Webhooks API 文档,了解如何接收实时事件通知
更新时间: 2024/12/30
Webhooks 允许您的应用程序接收 SmartCV 中发生的事件的实时通知,而无需持续轮询我们的 API。
🔔 概述
当特定事件在 SmartCV 中发生时,我们会向您配置的端点发送 HTTP POST 请求。这样您可以:
- 实时同步数据
- 自动触发工作流程
- 监控重要活动
- 集成第三方服务
📋 支持的事件
简历相关事件
事件类型 | 描述 |
---|---|
resume.created | 简历创建 |
resume.updated | 简历更新 |
resume.deleted | 简历删除 |
resume.shared | 简历分享 |
resume.exported | 简历导出 |
用户相关事件
事件类型 | 描述 |
---|---|
user.registered | 用户注册 |
user.subscription_changed | 订阅状态变化 |
user.profile_updated | 用户资料更新 |
AI 相关事件
事件类型 | 描述 |
---|---|
ai.analysis_completed | AI 分析完成 |
ai.content_generated | AI 内容生成完成 |
🔧 配置 Webhooks
通过 API 配置
端点: POST /api/webhooks
请求体:
{
"url": "https://yourapp.com/webhooks/smartcv",
"events": ["resume.created", "resume.updated", "ai.analysis_completed"],
"secret": "your_webhook_secret",
"active": true
}
响应:
{
"success": true,
"data": {
"id": "webhook_123",
"url": "https://yourapp.com/webhooks/smartcv",
"events": ["resume.created", "resume.updated", "ai.analysis_completed"],
"secret": "your_webhook_secret",
"active": true,
"createdAt": "2024-12-30T10:00:00Z"
}
}
管理 Webhooks
// 获取 Webhooks 列表
async function getWebhooks() {
const response = await fetch('/api/webhooks', {
credentials: 'include'
})
return response.json()
}
// 更新 Webhook
async function updateWebhook(webhookId, updates) {
const response = await fetch(`/api/webhooks/${webhookId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include',
body: JSON.stringify(updates)
})
return response.json()
}
// 删除 Webhook
async function deleteWebhook(webhookId) {
const response = await fetch(`/api/webhooks/${webhookId}`, {
method: 'DELETE',
credentials: 'include'
})
return response.json()
}
📨 Webhook 载荷
载荷结构
所有 Webhook 请求都包含以下结构:
{
"id": "event_123",
"type": "resume.created",
"created": "2024-12-30T10:00:00Z",
"data": {
/* 事件特定数据 */
},
"api_version": "2024-12-30"
}
事件示例
resume.created
{
"id": "event_123",
"type": "resume.created",
"created": "2024-12-30T10:00:00Z",
"data": {
"resume": {
"id": "resume_456",
"title": "软件工程师简历",
"status": "DRAFT",
"user_id": "user_789",
"template_id": "template_123",
"created_at": "2024-12-30T10:00:00Z"
}
},
"api_version": "2024-12-30"
}
ai.analysis_completed
{
"id": "event_124",
"type": "ai.analysis_completed",
"created": "2024-12-30T10:05:00Z",
"data": {
"analysis": {
"resume_id": "resume_456",
"overall_score": 85,
"keyword_match_score": 78,
"format_score": 92,
"content_score": 88,
"suggestions_count": 5
}
},
"api_version": "2024-12-30"
}
user.subscription_changed
{
"id": "event_125",
"type": "user.subscription_changed",
"created": "2024-12-30T10:10:00Z",
"data": {
"user": {
"id": "user_789",
"email": "[email protected]"
},
"subscription": {
"previous_type": "FREE",
"current_type": "PREMIUM",
"changed_at": "2024-12-30T10:10:00Z"
}
},
"api_version": "2024-12-30"
}
🔐 安全验证
签名验证
SmartCV 使用 HMAC SHA256 签名来确保 Webhook 的安全性。
请求头:
X-SmartCV-Signature: sha256=<signature>
X-SmartCV-Timestamp: <timestamp>
验证示例
Node.js
const crypto = require('crypto')
function verifyWebhookSignature(payload, signature, secret, timestamp) {
// 检查时间戳(防止重放攻击)
const currentTime = Math.floor(Date.now() / 1000)
const webhookTime = parseInt(timestamp)
if (Math.abs(currentTime - webhookTime) > 300) {
// 5分钟内有效
throw new Error('Webhook timestamp too old')
}
// 验证签名
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(timestamp + '.' + payload)
.digest('hex')
const actualSignature = signature.replace('sha256=', '')
if (!crypto.timingSafeEqual(Buffer.from(expectedSignature, 'hex'), Buffer.from(actualSignature, 'hex'))) {
throw new Error('Invalid webhook signature')
}
return true
}
// Express 中间件示例
app.post('/webhooks/smartcv', express.raw({ type: 'application/json' }), (req, res) => {
const payload = req.body
const signature = req.headers['x-smartcv-signature']
const timestamp = req.headers['x-smartcv-timestamp']
try {
verifyWebhookSignature(payload, signature, process.env.WEBHOOK_SECRET, timestamp)
const event = JSON.parse(payload)
handleWebhookEvent(event)
res.status(200).send('OK')
} catch (error) {
console.error('Webhook verification failed:', error)
res.status(400).send('Invalid webhook')
}
})
Python
import hmac
import hashlib
import time
import json
def verify_webhook_signature(payload, signature, secret, timestamp):
# 检查时间戳
current_time = int(time.time())
webhook_time = int(timestamp)
if abs(current_time - webhook_time) > 300: # 5分钟内有效
raise ValueError("Webhook timestamp too old")
# 验证签名
expected_signature = hmac.new(
secret.encode('utf-8'),
f"{timestamp}.{payload}".encode('utf-8'),
hashlib.sha256
).hexdigest()
actual_signature = signature.replace('sha256=', '')
if not hmac.compare_digest(expected_signature, actual_signature):
raise ValueError("Invalid webhook signature")
return True
# Flask 示例
from flask import Flask, request, jsonify
@app.route('/webhooks/smartcv', methods=['POST'])
def handle_webhook():
payload = request.get_data(as_text=True)
signature = request.headers.get('X-SmartCV-Signature')
timestamp = request.headers.get('X-SmartCV-Timestamp')
try:
verify_webhook_signature(payload, signature, WEBHOOK_SECRET, timestamp)
event = json.loads(payload)
handle_webhook_event(event)
return jsonify({'status': 'success'}), 200
except ValueError as e:
return jsonify({'error': str(e)}), 400
📝 事件处理示例
完整的 Webhook 处理器
class SmartCVWebhookHandler {
constructor(secret) {
this.secret = secret
}
async handleWebhook(payload, signature, timestamp) {
// 验证签名
this.verifySignature(payload, signature, timestamp)
const event = JSON.parse(payload)
// 根据事件类型分发处理
switch (event.type) {
case 'resume.created':
await this.handleResumeCreated(event.data)
break
case 'resume.updated':
await this.handleResumeUpdated(event.data)
break
case 'ai.analysis_completed':
await this.handleAnalysisCompleted(event.data)
break
case 'user.subscription_changed':
await this.handleSubscriptionChanged(event.data)
break
default:
console.log(`Unhandled event type: ${event.type}`)
}
}
async handleResumeCreated(data) {
const { resume } = data
console.log(`New resume created: ${resume.id}`)
// 同步到本地数据库
await this.syncResumeToDatabase(resume)
// 发送通知
await this.sendNotification({
type: 'resume_created',
message: `Resume "${resume.title}" has been created`,
userId: resume.user_id
})
}
async handleAnalysisCompleted(data) {
const { analysis } = data
console.log(`Analysis completed for resume: ${analysis.resume_id}`)
// 更新分析结果
await this.updateAnalysisResults(analysis)
// 如果评分较低,发送改进建议
if (analysis.overall_score < 70) {
await this.sendImprovementSuggestions(analysis)
}
}
async handleSubscriptionChanged(data) {
const { user, subscription } = data
console.log(
`User ${user.id} subscription changed from ${subscription.previous_type} to ${subscription.current_type}`
)
// 更新用户权限
await this.updateUserPermissions(user.id, subscription.current_type)
// 发送欢迎邮件(如果是升级)
if (subscription.current_type === 'PREMIUM' && subscription.previous_type === 'FREE') {
await this.sendWelcomePremiumEmail(user.email)
}
}
verifySignature(payload, signature, timestamp) {
const crypto = require('crypto')
const currentTime = Math.floor(Date.now() / 1000)
const webhookTime = parseInt(timestamp)
if (Math.abs(currentTime - webhookTime) > 300) {
throw new Error('Webhook timestamp too old')
}
const expectedSignature = crypto
.createHmac('sha256', this.secret)
.update(timestamp + '.' + payload)
.digest('hex')
const actualSignature = signature.replace('sha256=', '')
if (!crypto.timingSafeEqual(Buffer.from(expectedSignature, 'hex'), Buffer.from(actualSignature, 'hex'))) {
throw new Error('Invalid webhook signature')
}
}
}
// 使用示例
const webhookHandler = new SmartCVWebhookHandler(process.env.WEBHOOK_SECRET)
app.post('/webhooks/smartcv', express.raw({ type: 'application/json' }), async (req, res) => {
try {
await webhookHandler.handleWebhook(req.body, req.headers['x-smartcv-signature'], req.headers['x-smartcv-timestamp'])
res.status(200).send('OK')
} catch (error) {
console.error('Webhook error:', error)
res.status(400).send('Error processing webhook')
}
})
🔄 重试机制
重试策略
SmartCV 使用指数退避策略进行重试:
- 重试次数: 最多 3 次
- 重试间隔: 1s, 2s, 4s
- 超时时间: 每次请求 30 秒超时
成功标准
以下 HTTP 状态码被视为成功:
200
- OK201
- Created202
- Accepted204
- No Content
🧪 测试 Webhooks
测试端点
您可以使用以下工具测试 Webhook:
- ngrok
- 创建本地隧道
- Webhook.site - 在线测试工具
- RequestBin - 请求收集器
手动触发测试
// 发送测试 Webhook
async function sendTestWebhook(webhookId) {
const response = await fetch(`/api/webhooks/${webhookId}/test`, {
method: 'POST',
credentials: 'include'
})
return response.json()
}
📊 监控和调试
Webhook 日志
您可以查看 Webhook 的发送日志:
async function getWebhookLogs(webhookId, limit = 50) {
const response = await fetch(`/api/webhooks/${webhookId}/logs?limit=${limit}`, {
credentials: 'include'
})
return response.json()
}
日志响应示例
{
"success": true,
"data": [
{
"id": "delivery_123",
"event_id": "event_456",
"event_type": "resume.created",
"url": "https://yourapp.com/webhooks/smartcv",
"status_code": 200,
"response_time": 145,
"attempts": 1,
"delivered_at": "2024-12-30T10:00:00Z"
},
{
"id": "delivery_124",
"event_id": "event_457",
"event_type": "ai.analysis_completed",
"url": "https://yourapp.com/webhooks/smartcv",
"status_code": 500,
"response_time": 30000,
"attempts": 3,
"last_attempt_at": "2024-12-30T10:05:00Z",
"next_retry_at": null,
"error": "Connection timeout"
}
]
}
❌ 故障排除
常见问题
-
签名验证失败
- 检查密钥是否正确
- 确保使用原始请求体计算签名
- 检查时间戳是否在有效范围内
-
重复事件
- 实现幂等性处理
- 使用事件 ID 去重
-
连接超时
- 优化端点响应时间
- 异步处理长时间操作
最佳实践
- 快速响应: 在 30 秒内响应
- 幂等处理: 同一事件可能被发送多次
- 错误处理: 正确返回错误状态码
- 日志记录: 记录所有 Webhook 事件以便调试