本指南将帮助您集成我们的 Webhook 系统,以接收来自 YCloud 的实时事件通知。
开始使用
什么是 Webhook?
Webhook 是一种 HTTP 回调,允许 YCloud 向您的服务器发送实时事件通知。当特定事件发生时(例如消息状态更新或入站消息),YCloud 将自动向您配置的 URL 发送包含事件数据的 HTTP POST 请求。
基本要求
在实现 Webhook 之前,您需要:
一个公开可访问的 HTTP 端点
- 必须可从互联网访问
- 强烈建议使用 HTTPS 以确保安全(仍支持 HTTP)
- 不得使用内部/私有 IP 地址
流程概述
sequenceDiagram
participant Event as 事件发生
participant YCloud as YCloud Webhook 系统
participant Server as 您的服务器
Event->>YCloud: 1. 触发事件<br/>(消息状态更新/入站消息等)
YCloud->>Server: 2. 发送 HTTP POST 请求<br/>(包含事件数据)
Server->>Server: 3. 接收并处理事件
Server-->>YCloud: 4. 返回 2xx 状态码
alt 响应成功
YCloud->>YCloud: 投递成功
else 响应失败或超时
YCloud->>YCloud: 自动重试
end
流程说明:
- YCloud 上发生事件(例如,收到入站消息、状态更新)
- YCloud 向您的 Webhook URL 发送 HTTP POST 请求
- 您的服务器接收并处理事件
- 您的服务器响应
2xx状态码 - 如果请求失败,YCloud 将自动重试
实现 Webhook
步骤 1:创建 Webhook 端点
使用我们的 API 创建 Webhook 端点:
请求示例:
curl -X POST "https://api.ycloud.com/v2/webhookEndpoints" \
-H "Content-Type: application/json" \
-H "X-API-Key: YOUR_API_KEY" \
-d '{
"url": "https://api.yourcompany.com/webhook",
"description": "生产环境 Webhook 端点",
"enabledEvents": [
"whatsapp.message.updated"
],
"status": "active"
}'响应:
{
"id": "wep_1234567890abcdef",
"url": "https://api.yourcompany.com/webhook",
"description": "生产环境 Webhook 端点",
"enabledEvents": [
"whatsapp.message.updated"
],
"status": "active",
"secret": "whsec_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6",
"createTime": "2025-11-03T10:00:00.000+08:00",
"updateTime": "2025-11-03T10:00:00.000+08:00"
}重要提示: 请安全地保存 secret 值。您需要使用它来验证 Webhook 签名。
有关端点 API 的详细信息,请参阅 Webhook 端点。
端点限制:
- 每个账户最多 20 个 Webhook 端点
- URL 必须公开可访问(不能使用内部 IP)
- URL 最大长度:500 个字符
- 描述最大长度:400 个字符
步骤 2:处理 Webhook 请求
当事件发生时,YCloud 将向您的 Webhook URL 发送 POST 请求:
请求头:
Content-Type: application/json
YCloud-Signature: t=1762224357,s=a1b2c3d4e5f6...
X-Webhook-Endpoint-ID: 6c31924eae964fafbf0c19efc20d6f4d
请求体示例:
{
"id": "evt_1234567890abcdef",
"type": "whatsapp.message.updated",
"apiVersion": "v2",
"createTime": "2025-11-03T10:00:00.000+08:00",
"whatsappMessage": {
"id": "wamid.1234567890",
"wabaId": "123456789",
"from": "+8613800138000",
"to": "+8613900139000",
"type": "text",
"status": "delivered",
"text": {
"body": "Hello, World!"
},
"createTime": "2025-11-03T09:59:00.000+08:00",
"updateTime": "2025-11-03T10:00:00.000+08:00"
}
}事件通用结构:
id:唯一事件标识符type:事件类型(例如whatsapp.message.updated)apiVersion:API 版本(当前为v2)createTime:事件创建时间戳{eventType}:事件特定数据(例如whatsappMessage、smsMessage)
有关事件特定数据,请参阅事件类型和载荷。
步骤 3:验证 Webhook 签名
始终验证签名以确保请求来自 YCloud 且未被篡改。
签名格式:
YCloud-Signature: t={timestamp},s={signature}
验证算法:
- 从请求头中提取时间戳(
t)和签名(s)(时间戳是以秒为单位的 Unix 时间戳) - 构造签名载荷:
{timestamp}.{request_body} - 计算 HMAC-SHA256:
HMAC-SHA256(signed_payload, secret) - 将计算出的签名与接收到的签名进行比较
示例(伪代码):
function verifySignature(payload, signatureHeader, secret) {
// 解析请求头
const parts = signatureHeader.split(',');
const timestamp = parts[0].split('=')[1];
const signature = parts[1].split('=')[1];
// 构造签名载荷
const signedPayload = `${timestamp}.${payload}`;
// 计算预期签名
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest('hex');
// 比较签名(生产环境中使用恒定时间比较)
return signature === expectedSignature;
}步骤 4:响应 Webhook
响应要求:
-
返回
2xx状态码(例如200、201、204)- 任何非 2xx 响应都会触发重试
-
快速响应(建议在 6 秒内)
- 快速响应可提高您的 Webhook 优先级
- 慢速响应(>10 秒)可能会被降低优先级
-
异步处理(推荐)
- 立即返回
200 OK - 在后台作业/队列中处理事件
- 立即返回
响应示例:
HTTP/1.1 200 OK
Content-Type: application/json
{
"received": true
}注意: 响应体会被忽略。只有状态码重要。
实现示例
const express = require('express');
const crypto = require('crypto');
const app = express();
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;
// 中间件:捕获原始请求体以进行签名验证
app.use(express.json({
verify: (req, res, buf) => {
req.rawBody = buf.toString('utf8');
}
}));
// Webhook 端点
app.post('/webhook', (req, res) => {
const signature = req.headers['ycloud-signature'];
const payload = req.rawBody;
// 验证签名
if (!verifySignature(payload, signature, WEBHOOK_SECRET)) {
console.error('签名无效');
return res.status(401).send('未授权');
}
// 处理事件
const event = req.body;
console.log('收到事件:', event.type, event.id);
// 处理不同的事件类型
switch (event.type) {
case 'whatsapp.message.updated':
handleWhatsAppMessage(event.whatsappMessage);
break;
case 'whatsapp.inbound.message':
handleInboundMessage(event.whatsappInboundMessage);
break;
default:
console.log('未处理的事件类型:', event.type);
}
// 立即响应
res.status(200).json({ received: true });
});
function verifySignature(payload, signatureHeader, secret) {
if (!signatureHeader) return false;
const parts = signatureHeader.split(',');
const timestamp = parts[0].split('=')[1];
const signature = parts[1].split('=')[1];
const signedPayload = `${timestamp}.${payload}`;
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest('hex');
// 使用恒定时间比较
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
function handleWhatsAppMessage(message) {
// 异步处理消息
console.log('WhatsApp 消息状态:', message.status);
// 添加到队列、更新数据库等
}
function handleInboundMessage(message) {
// 处理入站消息
console.log('收到来自以下用户的消息:', message.from);
// 添加到队列、触发自动回复等
}
app.listen(3000, () => {
console.log('Webhook 服务器正在监听端口 3000');
});import org.springframework.web.bind.annotation.*;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
@RestController
public class WebhookController {
private static final String WEBHOOK_SECRET = "<YOUR_WEBHOOK_SECRET>";
@PostMapping("/webhook")
public ResponseEntity<Map<String, Boolean>> handleWebhook(
@RequestHeader("YCloud-Signature") String signature,
@RequestBody String payload) {
// 验证签名
if (!verifySignature(payload, signature, WEBHOOK_SECRET)) {
return ResponseEntity.status(401).build();
}
// 解析事件
ObjectMapper mapper = new ObjectMapper();
JsonNode event = mapper.readTree(payload);
String eventType = event.get("type").asText();
String eventId = event.get("id").asText();
System.out.println("收到事件: " + eventType + " - " + eventId);
// 处理不同的事件类型
switch (eventType) {
case "whatsapp.message.updated":
handleWhatsAppMessage(event.get("whatsappMessage"));
break;
case "whatsapp.inbound.message":
handleInboundMessage(event.get("whatsappInboundMessage"));
break;
default:
System.out.println("未处理的事件类型: " + eventType);
}
// 立即响应
return ResponseEntity.ok(Map.of("received", true));
}
private boolean verifySignature(String payload, String signatureHeader, String secret) {
try {
// 解析请求头
String[] parts = signatureHeader.split(",");
String timestamp = parts[0].split("=")[1];
String signature = parts[1].split("=")[1];
// 计算预期签名
String signedPayload = timestamp + "." + payload;
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKey = new SecretKeySpec(
secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
mac.init(secretKey);
byte[] hash = mac.doFinal(signedPayload.getBytes(StandardCharsets.UTF_8));
String expectedSignature = bytesToHex(hash);
// 恒定时间比较
return MessageDigest.isEqual(
signature.getBytes(StandardCharsets.UTF_8),
expectedSignature.getBytes(StandardCharsets.UTF_8)
);
} catch (Exception e) {
return false;
}
}
private String bytesToHex(byte[] bytes) {
StringBuilder result = new StringBuilder();
for (byte b : bytes) {
result.append(String.format("%02x", b));
}
return result.toString();
}
private void handleWhatsAppMessage(JsonNode message) {
// 异步处理消息
System.out.println("WhatsApp 消息状态: " + message.get("status").asText());
// 添加到队列、更新数据库等
}
private void handleInboundMessage(JsonNode message) {
// 处理入站消息
System.out.println("收到来自以下用户的消息: " + message.get("from").asText());
// 添加到队列、触发自动回复等
}
}重要注意事项
最佳实践
-
验证签名 - 始终验证
YCloud-Signature请求头以确保请求来自 YCloud -
使用 HTTPS - 建议用于加密传输中的数据并防止攻击
-
快速响应 - 在 6 秒内返回
200 OK以获得最佳投递优先级 -
处理幂等性 - 使用事件 ID 检测并跳过重复事件
-
异步处理 - 立即返回
200 OK,然后在后台工作进程中处理事件
幂等性示例:
const Redis = require('ioredis');
const redis = new Redis();
app.post('/webhook', async (req, res) => {
const event = req.body;
const eventId = event.id;
// 检查是否已处理(TTL:7 天)
const exists = await redis.get(`webhook:processed:${eventId}`);
if (exists) {
console.log('重复事件,跳过:', eventId);
return res.status(200).json({ received: true });
}
// 标记为已处理
await redis.setex(`webhook:processed:${eventId}`, 7 * 24 * 3600, '1');
// 异步处理事件
await queue.add('process-webhook', event);
res.status(200).json({ received: true });
});@Autowired
private RedisTemplate<String, String> redisTemplate;
@PostMapping("/webhook")
public ResponseEntity<Map<String, Boolean>> handleWebhook(@RequestBody String payload) {
JsonNode event = objectMapper.readTree(payload);
String eventId = event.get("id").asText();
// 检查是否已处理
String key = "webhook:processed:" + eventId;
Boolean exists = redisTemplate.hasKey(key);
if (Boolean.TRUE.equals(exists)) {
logger.info("重复事件,跳过: {}", eventId);
return ResponseEntity.ok(Map.of("received", true));
}
// 标记为已处理(TTL:7 天)
redisTemplate.opsForValue().set(key, "1", 7, TimeUnit.DAYS);
// 异步处理事件
eventQueue.send(event);
return ResponseEntity.ok(Map.of("received", true));
}错误处理
重试机制:
如果您的服务器返回非 2xx 状态码或未响应,YCloud 将自动重试:
- 重试计划: 10秒 → 30秒 → 5分钟 → 30分钟 → 1小时 → 2小时 → 2小时
- 最大重试次数: 7 次
- 7 次失败后: 该事件不再重试
URL 暂停:
为了保护系统资源,频繁失败的 URL 将被临时暂停:
- 触发条件: 每分钟 200 次失败 或 每分钟累计失败时间 10 分钟
- 暂停时长: 3 分钟
- 暂停期间: 不会发送 Webhook 请求
- 暂停后: 自动恢复
最佳实践:
- 监控您的 Webhook 端点的正常运行时间
- 为 Webhook 失败设置警报
- 即使处理失败也返回
200 OK(在内部处理重试)
附加资源
- API 参考: Webhook API 文档
- 事件类型参考: 事件类型文档
