什么是 Webhooks?
Webhooks 是 KillB 在事件发生时发送到您服务器的 HTTP 回调。它们能够实时通知 ramps、用户、账户和交易,而无需持续轮询。
Webhooks 是监控交易状态并保持应用程序与 KillB 同步的推荐方式。
Webhooks 如何工作
Webhook 事件结构
所有 webhook 事件都遵循此结构:
{
id: string, // UUID - 唯一事件 ID
event: string, // 事件类型:RAMP | USER | TRANSACTION | ACCOUNT | CUSTODIAL_ACCOUNT
action: string, // 操作:CREATE | UPDATE | DELETE
data: object, // 事件特定数据
createdAt: string, // ISO 8601 时间戳
updatedAt: string, // ISO 8601 时间戳
attempts: number // 交付尝试次数(0 到 5)
}
Webhook 事件
KillB 支持以下事件类型:
RAMP
USER
ACCOUNT
TRANSACTION
CUSTODIAL_ACCOUNT
Ramp 交易事件事件类型: RAMP操作:
CREATE - 创建新 ramp
UPDATE - Ramp 状态已更改
DELETE - Ramp 已取消(罕见)
常见状态值:
QUOTE_CREATED - 报价已创建
PENDING_PAYMENT - 等待支付
CASH_IN_PROCESSING - 正在处理支付
CASH_IN_COMPLETED - 支付已确认
PROCESSING - 正在转换货币
CASH_OUT_PROCESSING - 正在发送资金
COMPLETED - Ramp 已完成
FAILED - Ramp 失败
CANCELED - Ramp 已取消
用户和 KYC 事件事件类型: USER操作:
CREATE - 新用户注册
UPDATE - 用户信息或 KYC 状态已更改
DELETE - 用户已删除(罕见)
状态值:
PENDING - 等待验证
ACTIVE - 已验证并激活
REJECTED - KYC 被拒绝
SUSPENDED - 账户已暂停
访问级别:
L0 - 未验证
L1 - 基本验证
L2 - 标准验证
L3 - 增强验证
L4 - 高级验证
账户验证事件事件类型: ACCOUNT操作:
CREATE - 添加新账户
UPDATE - 账户状态或详情已更改
DELETE - 账户已删除
账户类型:
PSE - 哥伦比亚银行账户
SPEI - 墨西哥银行账户
ACH - 美国银行账户
WIRE - 电汇账户
WALLET - 加密货币钱包
PIX - 巴西 PIX 账户
TRANSFIYA - 哥伦比亚移动钱包
状态值:
PENDING - 等待验证
ACTIVE - 已验证并激活
REJECTED - 验证失败
INACTIVE - 已禁用
储蓄账户交易事件类型: TRANSACTION操作:
CREATE - 启动新交易
UPDATE - 交易状态已更改
DELETE - 交易已取消(罕见)
交易类型:
DEPOSIT - 储蓄存款
WITHDRAWAL - 储蓄提款
INTEREST - 利息支付
FEE - 费用收取
托管账户事件事件类型: CUSTODIAL_ACCOUNT操作:
CREATE - 创建托管账户
UPDATE - 余额或状态已更新
DELETE - 账户已关闭(罕见)
更改原因:
DEPOSIT - 已存入资金
WITHDRAWAL - 已提取资金
INTEREST - 已记入利息
FEE - 已收取费用
ADJUSTMENT - 手动调整
设置 Webhooks
创建 Webhook 配置
请求:
{
"url": "https://api.yourapp.com/webhooks/killb",
"secret": "your-secret-key-min-32-chars",
"events": ["RAMP", "USER", "ACCOUNT"]
}
响应:
{
"id": "webhook-id",
"customerId": "customer-id",
"url": "https://api.yourapp.com/webhooks/killb",
"events": ["RAMP", "USER", "ACCOUNT"],
"active": true,
"createdAt": "2024-01-15T10:30:00.000Z"
}
const setupWebhook = async () => {
const response = await fetch('https://teste-94u93qnn.uc.gateway.dev/api/v2/webhooks', {
method: 'POST',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
url: 'https://api.yourapp.com/webhooks/killb',
secret: process.env.WEBHOOK_SECRET,
events: ['RAMP', 'USER', 'ACCOUNT', 'TRANSACTION']
})
});
return await response.json();
};
Webhook 安全
签名验证
KillB 使用 HMAC SHA-256 对所有 webhook 请求进行签名。始终验证签名以确保真实性。
标头: x-signature-sha256
const crypto = require('crypto');
const express = require('express');
app.post('/webhooks/killb', express.raw({type: 'application/json'}), (req, res) => {
const signature = req.headers['x-signature-sha256'];
const payload = req.body.toString();
// 计算预期签名
const expectedSignature = crypto
.createHmac('sha256', process.env.WEBHOOK_SECRET)
.update(payload)
.digest('hex');
// 验证签名
if (signature !== expectedSignature) {
console.error('Invalid webhook signature');
return res.status(401).json({ error: 'Invalid signature' });
}
// 解析并处理事件
const event = JSON.parse(payload);
processWebhookEvent(event);
res.status(200).json({ received: true });
});
永远不要跳过签名验证! 未验证的 webhooks 可能被攻击者伪造。
Webhook 要求
您的 webhook 端点必须:
- 在 5 秒内返回
200 OK
- 异步处理事件
- 不要等待慢速操作
- 对繁重处理使用作业队列
- 幂等(处理重复事件)
- 使用事件 ID 检测重复
- 存储已处理的事件 ID
- 跳过已处理的事件
- 保持 >99% 正常运行时间
- 仅使用 HTTPS
- 拥有有效的 SSL 证书
- 使用适当的状态代码响应
- 始终检查
x-signature-sha256
- 使用恒定时间比较
- 拒绝无效签名
- 记录验证失败
重试逻辑
KillB 使用指数退避自动重试失败的 webhook 交付:
重试计划:
- 尝试 0:立即交付
- 尝试 1:1 分钟后
- 尝试 2:5 分钟后
- 尝试 3:15 分钟后
- 尝试 4:1 小时后
- 尝试 5:6 小时后(最终尝试)
失败标准:
- 非 2xx 响应代码
- 连接超时(> 5 秒)
- 网络错误
webhook 有效负载中的 attempts 字段指示当前交付尝试(0 到 5)。
// 检查重试次数
if (webhookEvent.attempts > 3) {
console.warn(`High retry count: ${webhookEvent.attempts}`);
// 提醒监控系统
}
尝试 5 次失败后,webhook 被标记为永久失败,需要手动干预。检查您的 webhook 日志并修复任何问题。
测试 Webhooks
使用 ngrok 进行本地测试
# 安装 ngrok
npm install -g ngrok
# 启动本地服务器
node server.js
# 暴露到互联网
ngrok http 3000
# 在 webhook 配置中使用 ngrok URL
# https://abc123.ngrok.io/webhooks/killb
手动测试
在沙盒中触发测试事件:
// 创建测试 ramp
const ramp = await createRamp(quoteId, userId, accountId);
// 模拟完成(触发 webhooks)
await fetch('/api/v2/faker/cash-in', {
method: 'POST',
headers: { 'Authorization': `Bearer ${token}` },
body: JSON.stringify({ rampId: ramp.id })
});
// 您的 webhook 应该收到 ramp.cash_in_completed 事件
管理 Webhooks
获取 Webhook 配置
返回您当前的 webhook 配置。
更新 Webhook
更新:
{
"url": "https://api.yourapp.com/webhooks/killb-v2",
"active": true,
"events": ["RAMP", "USER"]
}
删除 Webhook
删除 webhook 配置。将不再发送事件。
事件处理
示例处理程序
const processWebhookEvent = (webhookEvent) => {
const { id, event, action, data, attempts } = webhookEvent;
console.log(`Processing ${event}.${action} (attempt ${attempts})`);
// 根据事件类型和操作路由
switch(event) {
case 'RAMP':
handleRampEvent(action, data);
break;
case 'USER':
handleUserEvent(action, data);
break;
case 'ACCOUNT':
handleAccountEvent(action, data);
break;
case 'TRANSACTION':
handleTransactionEvent(action, data);
break;
case 'CUSTODIAL_ACCOUNT':
handleCustodialEvent(action, data);
break;
default:
console.log(`Unhandled event type: ${event}`);
}
};
const handleRampEvent = async (action, data) => {
if (action === 'UPDATE' && data.status === 'COMPLETED') {
// 更新数据库
await db.ramps.update(data.id, {
status: data.status,
updatedAt: data.updatedAt,
transferProof: data.transferProof
});
// 通知用户
await sendEmail(data.userId, 'Transaction Completed', {
amount: data.toAmount,
currency: data.toCurrency,
txHash: data.transferProof
});
// 更新内部系统
await updateAccounting(data);
}
};
const handleUserEvent = async (action, data) => {
if (action === 'UPDATE') {
// 检查 KYC 级别是否更改
await db.users.update(data.id, {
status: data.status,
accessLevel: data.accessLevel,
updatedAt: data.updatedAt
});
// 如果 KYC 已批准,启用功能
if (data.status === 'ACTIVE' && data.accessLevel !== 'L0') {
await enableUserFeatures(data.id, data.accessLevel);
}
}
};
const handleAccountEvent = async (action, data) => {
if (action === 'UPDATE' && data.status === 'ACTIVE') {
// 账户已验证
await db.accounts.update(data.id, {
status: data.status,
verified: true
});
// 通知用户他们的账户已准备就绪
await sendNotification(data.userId, 'Account Verified');
}
};
幂等性
使用事件 ID 处理重复的 webhook 交付:
const handleWebhook = async (webhookEvent) => {
const { id, event, action, data, attempts } = webhookEvent;
// 检查是否已处理(使用唯一事件 ID)
const existing = await db.webhookEvents.findOne({ eventId: id });
if (existing) {
console.log(`Duplicate event ${id}, skipping`);
return { received: true, duplicate: true };
}
// 立即存储事件以防止重复处理
await db.webhookEvents.create({
eventId: id,
eventType: event,
action: action,
payload: data,
attempts: attempts,
receivedAt: new Date(),
processed: false
});
try {
// 处理事件
await processWebhookEvent(webhookEvent);
// 标记为已处理
await db.webhookEvents.update(
{ eventId: id },
{ processed: true, processedAt: new Date() }
);
return { received: true };
} catch (error) {
// 记录错误但仍标记为已接收
await db.webhookEvents.update(
{ eventId: id },
{ error: error.message, failedAt: new Date() }
);
throw error;
}
};
最佳实践
app.post('/webhooks/killb', async (req, res) => {
// 验证签名
verifySignature(req);
// 立即确认
res.status(200).json({ received: true });
// 异步处理
processAsync(req.body);
});
- 立即返回 200 OK
- 在后台处理事件
- 使用作业队列(Bull、Celery 等)
- 不要等待外部服务
const processWebhook = async (event) => {
try {
await processEvent(event);
} catch (error) {
console.error('Webhook processing failed:', error);
// 记录到监控服务
await logError(error, event);
// 不要抛出 - 我们已经处理了
// KillB 无论如何都会重试
}
};
- 捕获所有错误
- 记录失败
- 确认后不要抛出
- 监控错误率
await db.webhookLogs.create({
eventId: event.id,
eventType: event.event,
payload: event.data,
receivedAt: new Date(),
processed: true
});
- 保留审计跟踪
- 如果需要,启用重放
- 轻松调试问题
- 满足合规要求
- 跟踪交付成功率
- 多次失败时提醒
- 监控处理时间
- 设置正常运行时间监控
- 检查签名验证失败
Webhook 安全清单
仅使用 HTTPS
永远不要对 webhook 端点使用 HTTP✅ https://api.yourapp.com/webhooks
❌ http://api.yourapp.com/webhooks
验证每个请求
在每个 webhook 上检查 x-signature-sha256
使用强密钥
最少 32 个字符,随机,安全存储const secret = crypto.randomBytes(32).toString('hex');
故障排除
检查:
- Webhook URL 可公开访问
- SSL 证书有效
- 防火墙允许 KillB IP
- 端点快速返回 200 OK
- 没有反向代理问题
测试:curl -X POST https://your-webhook-url \
-H "Content-Type: application/json" \
-d '{"test": "data"}'
常见问题:
- 使用错误的密钥
- 在验证之前解析正文
- 字符集/编码问题
- 空白修改
解决方案:// 使用原始正文
app.use('/webhooks', express.raw({type: 'application/json'}));
// 在解析之前验证
verifySignature(req.body.toString(), signature);
// 然后解析
const event = JSON.parse(req.body);
这是正常的! Webhooks 可能会多次交付。使用以下方式处理:
- 事件 ID 跟踪
- 数据库唯一约束
- 幂等性密钥
- “已处理”标志
高级配置
过滤事件
仅订阅您需要的事件:
{
"url": "https://api.yourapp.com/webhooks/killb",
"secret": "your-secret",
"events": ["RAMP"]
}
多个 Webhooks
为不同的事件类型创建不同的端点:
// Ramp 事件 → 支付处理器
await createWebhook({
url: 'https://payments.yourapp.com/webhooks',
events: ['RAMP']
});
// 用户事件 → CRM
await createWebhook({
url: 'https://crm.yourapp.com/webhooks',
events: ['USER']
});
每个客户只能有一个活动的 webhook 配置。使用单个端点并在内部路由。
Webhook 监控
// 跟踪 webhook 健康
const webhookMetrics = {
received: 0,
processed: 0,
failed: 0,
averageProcessingTime: 0
};
app.post('/webhooks/killb', async (req, res) => {
const startTime = Date.now();
webhookMetrics.received++;
try {
await processWebhook(req.body);
webhookMetrics.processed++;
} catch (error) {
webhookMetrics.failed++;
throw error;
} finally {
const duration = Date.now() - startTime;
webhookMetrics.averageProcessingTime =
(webhookMetrics.averageProcessingTime + duration) / 2;
}
res.status(200).json({ received: true });
});
下一步