Webhook 回调

接收和处理支付事件的实时通知

7 分钟阅读
更新于 2025/8/15
WebhookAPI通知

Webhook 是 Payve 向您的服务器推送支付事件实时通知的机制。当支付状态发生变化时,我们会向您预先配置的回调 URL 发送 HTTP POST 请求。

概述

Webhook 通知采用 至少一次投递(At-Least-Once) 语义,确保重要的支付事件不会丢失。您的服务器需要正确处理重复通知,实现幂等性。

关键特性

  • 实时通知 - 支付确认后立即推送
  • 可靠投递 - 自动重试机制,最长重试 72 小时
  • 安全验证 - HMAC-SHA256 签名验证
  • 幂等去重 - 唯一事件 ID 防止重复处理
  • 智能重试 - 指数退避算法,避免系统过载

快速开始

1. 配置 Webhook 端点

在商户控制台设置您的 Webhook 接收地址:

https://your-domain.com/api/webhooks/payve

2. 获取 Webhook 密钥

系统会为您生成一个唯一的 Webhook 密钥,用于验证请求签名。请妥善保管此密钥。

3. 实现接收端点

请求格式

HTTP 请求头

请求头说明示例
X-PGW-Event-Id全局唯一事件 IDevt_1a2b3c4d5e6f
X-PGW-Delivery-Id本次投递 IDdlv_9z8y7x6w5v4u
X-PGW-Attempt重试次数(从1开始)1
X-PGW-TimestampUnix 时间戳(秒)1708123456
X-PGW-SignatureHMAC-SHA256 签名t=1708123456, v1=abc123...
X-PGW-Signature-Alg签名算法HMAC-SHA256
User-Agent客户端标识Payve-Webhook/1.0
Content-Type内容类型application/json

请求体格式

事件类型

事件类型说明触发时机
payment.succeeded支付成功收到足额支付并确认
payment.failed支付失败支付失败或超时
payment.expired支付过期超过支付时限未收到支付
payment.refunded退款成功退款处理完成

签名验证

验证算法

签名使用 HMAC-SHA256 算法,计算方式:

重要提示

  1. 必须使用原始请求体(raw body)进行签名验证,不要使用解析后的 JSON
  2. 验证时间戳防止重放攻击,建议设置 5 分钟的时间窗口
  3. 使用恒定时间比较防止时序攻击

响应要求

成功响应

HTTP 状态码: 200-299

{
  "status": "ok"
}

⚠️ 重要: 必须在 5 秒内 返回 2xx 状态码,否则视为失败并触发重试。

最佳实践

  1. 快速响应 - 收到请求后立即返回 2xx,然后异步处理业务逻辑
  2. 幂等处理 - 使用 event_id 去重,避免重复处理同一事件
  3. 错误处理 - 临时错误返回 5xx 触发重试,永久错误返回 4xx

重试机制

重试策略

当 Webhook 调用失败时(非 2xx 响应、超时、网络错误),系统会自动重试:

重试次数延迟时间累计时间
11 分钟1 分钟
25 分钟6 分钟
315 分钟21 分钟
41 小时1.3 小时
53 小时4.3 小时
66 小时10.3 小时
712 小时22.3 小时
824 小时46.3 小时
924 小时70.3 小时

📝 说明:

  • 最多重试 10 次或 72 小时(以先到达者为准)
  • 每次重试间隔会加入 ±20% 的随机抖动,避免雪崩效应
  • 如果收到 429 Too Many Requests 响应,将遵守 Retry-After 头部

特殊状态码处理

HTTP 状态码处理方式说明
2xx成功停止重试,标记为成功
408, 425, 429, 5xx重试按照重试策略继续尝试
410 Gone停止端点已失效,停止重试并禁用 Webhook
其他 4xx有限重试重试 3 次后停止

安全建议

1. 验证所有请求

// 始终验证签名
if (!verifySignature(req)) {
  return res.status(401).send('Unauthorized');
}

2. 使用 HTTPS

确保您的 Webhook 端点使用 HTTPS 协议,避免中间人攻击。

3. IP 白名单(可选)

如需额外安全保障,可以将 Payve 的 IP 地址加入白名单:

待更新 IP 列表

4. 密钥轮换

定期更换 Webhook 密钥,建议每 90 天轮换一次。

实现示例

Node.js (Express)

Python (Flask)

PHP

测试工具

使用 ngrok 进行本地测试

  1. 安装 ngrok:
brew install ngrok  # macOS
# 或访问 https://ngrok.com/download
  1. 暴露本地端口:
ngrok http 3000
  1. 使用 ngrok 提供的 HTTPS URL 作为 Webhook 端点

模拟 Webhook 请求

故障排查

常见问题

1. 签名验证失败

可能原因:

  • 使用了解析后的 JSON 而非原始请求体
  • Webhook 密钥不正确
  • 请求体在传输过程中被修改

解决方案:

  • 确保使用原始请求体(raw body)进行签名验证
  • 检查 Webhook 密钥是否正确
  • 记录并对比收到的请求体和签名

2. 重复接收通知

可能原因:

  • 未正确实现幂等性
  • 处理时间过长导致超时重试

解决方案:

  • 使用 event_id 实现幂等性检查
  • 确保在 5 秒内返回响应
  • 异步处理耗时操作

3. 收不到 Webhook 通知

可能原因:

  • Webhook URL 配置错误
  • 防火墙或安全组阻止了请求
  • SSL 证书问题

解决方案:

  • 检查 Webhook URL 是否正确且可访问
  • 确保服务器允许来自 Payve 的请求
  • 使用有效的 SSL 证书

日志记录建议

建议记录以下信息用于故障排查:

console.log({
  event_id: req.headers['x-pgw-event-id'],
  delivery_id: req.headers['x-pgw-delivery-id'],
  attempt: req.headers['x-pgw-attempt'],
  timestamp: req.headers['x-pgw-timestamp'],
  signature_valid: isSignatureValid,
  response_time: responseTime,
  processing_result: result
});

监控和告警

建议监控指标

  1. Webhook 接收成功率 - 监控 2xx 响应比例
  2. 响应时间 - 确保 P95 < 5 秒
  3. 重复事件数量 - 监控幂等性过滤的事件数
  4. 签名验证失败 - 可能的安全问题

告警阈值建议

  • 连续 5 次 Webhook 失败
  • 响应时间超过 4 秒
  • 签名验证失败率 > 1%
  • 24 小时内无 Webhook 接收(业务活跃时)

API 参考

查询 Webhook 投递状态

GET /api/v1/webhooks/deliveries/{event_id}
Authorization: Bearer YOUR_API_KEY

响应示例:

{
  "event_id": "evt_1a2b3c4d5e6f",
  "status": "SUCCESS",
  "attempts": 1,
  "last_attempt_at": "2024-02-17T10:30:50Z",
  "next_retry_at": null,
  "response_code": 200,
  "response_time_ms": 150
}

重新发送 Webhook

POST /api/v1/webhooks/resend
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
 
{
  "event_id": "evt_1a2b3c4d5e6f"
}

更新 Webhook 配置

PUT /api/v1/merchant/webhook
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json
 
{
  "url": "https://new-domain.com/webhooks",
  "enabled": true
}