事件结构
所有 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)
}
使用 id 字段实现幂等性。attempts 字段指示重试次数。
事件类型
RAMP 事件
类型: RAMP
操作:
CREATE - Ramp 已创建
UPDATE - Ramp 状态已更改
DELETE - Ramp 已取消(罕见)
示例 - Ramp 已创建:
{
"id": "evt_a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"event": "RAMP",
"action": "CREATE",
"data": {
"id": "be4d353b-00a2-4309-9ef1-594f37dfb1fd",
"userId": "2bc703f2-1c54-4cbb-a144-993e1d957688",
"accountId": "f7df00af-5d12-4b33-b5ab-e2b32290ebad",
"quotationId": "a656a475-0d53-479e-8f30-91582ecf450b",
"type": "ON",
"status": "CREATED",
"cashInMethod": "PSE",
"cashOutMethod": "POLYGON",
"fromCurrency": "COP",
"toCurrency": "USDC",
"fromAmount": 100000,
"toAmount": 23.81,
"isPreFunded": false,
"active": true,
"createdAt": "2025-01-15T23:46:10.226Z",
"updatedAt": "2025-01-15T23:46:10.226Z"
},
"createdAt": "2025-01-15T23:46:10.226Z",
"updatedAt": "2025-01-15T23:46:10.226Z",
"attempts": 0
}
示例 - Ramp 已完成:
{
"id": "evt_b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e",
"event": "RAMP",
"action": "UPDATE",
"data": {
"id": "be4d353b-00a2-4309-9ef1-594f37dfb1fd",
"userId": "2bc703f2-1c54-4cbb-a144-993e1d957688",
"accountId": "f7df00af-5d12-4b33-b5ab-e2b32290ebad",
"type": "ON",
"status": "COMPLETED",
"fromCurrency": "COP",
"toCurrency": "USDC",
"fromAmount": 100000,
"toAmount": 23.81,
"transferProof": "0x8e2b1f4645ebc8b6f658c860980567a63d283a42735be7401a7950306a2539b0",
"createdAt": "2025-01-15T23:46:10.226Z",
"updatedAt": "2025-01-16T00:29:06.813Z"
},
"createdAt": "2025-01-16T00:29:06.813Z",
"updatedAt": "2025-01-16T00:29:06.813Z",
"attempts": 0
}
处理 Ramp 事件:
if (webhookEvent.event === 'RAMP' && webhookEvent.action === 'UPDATE') {
const { status, transferProof } = webhookEvent.data;
switch(status) {
case 'CASH_IN_COMPLETED':
console.log('支付已收到');
break;
case 'COMPLETED':
console.log('Ramp 已完成:', transferProof);
await notifyUser(webhookEvent.data.userId);
break;
case 'FAILED':
console.log('Ramp 失败');
await handleFailure(webhookEvent.data);
break;
}
}
常见状态:
CREATED - Ramp 已初始化
CASH_IN_PROCESSING - 正在处理支付
CASH_IN_COMPLETED - 支付已确认
CONVERSION_PROCESSING - 正在转换货币
CONVERSION_COMPLETED - 转换完成
CASH_OUT_PROCESSING - 正在发送资金
COMPLETED - 完全完成
FAILED - 失败
CANCELED - 已取消
USER 事件
类型: USER
操作:
CREATE - 用户已注册
UPDATE - 用户信息或状态已更改
DELETE - 用户已删除(罕见)
示例 - KYC 已批准:
{
"id": "evt_f1e2d3c4-b5a6-4978-8c9d-0e1f2a3b4c5d",
"event": "USER",
"action": "UPDATE",
"data": {
"id": "e3d5c4ca-839a-4067-af76-89b33b19696e",
"type": "PERSON",
"customerId": "c650925e-a4aa-4a5f-a0a9-6c97be6c6ce5",
"externalId": "user-ext-12345",
"status": "ACTIVE",
"accessLevel": "L2",
"data": {
"firstName": "Carlos",
"lastName": "Rodriguez",
"email": "[email protected]",
"phone": "+573001234567"
}
},
"createdAt": "2025-01-15T10:30:00.000Z",
"updatedAt": "2025-01-15T14:22:00.000Z",
"attempts": 0
}
处理用户事件:
if (webhookEvent.event === 'USER' && webhookEvent.action === 'UPDATE') {
const { status, accessLevel } = webhookEvent.data;
if (status === 'ACTIVE' && accessLevel !== 'L0') {
// KYC 已批准 - 启用功能
await enableUserFeatures(webhookEvent.data.id, accessLevel);
}
if (status === 'REJECTED') {
// KYC 被拒绝 - 通知用户
await sendKYCRejectionEmail(webhookEvent.data);
}
}
用户状态:
PENDING - 等待验证
ACTIVE - 已验证并激活
REJECTED - KYC 被拒绝
SUSPENDED - 账户已暂停
访问级别:
L0 - 未验证
L1 - 基本(每日 $1K)
L2 - 标准(每日 $10K)
L3 - 增强(每日 $50K)
L4 - 高级(无限制)
ACCOUNT 事件
类型: ACCOUNT
操作:
CREATE - 账户已添加
UPDATE - 账户状态已更改
DELETE - 账户已删除
示例 - 账户已验证:
{
"id": "evt_c9b8a7f6-d5e4-4321-9876-543210fedcba",
"event": "ACCOUNT",
"action": "UPDATE",
"data": {
"id": "543ab81d-0b1e-4b9d-88bc-58ba5a365f16",
"type": "PSE",
"status": "ACTIVE",
"externalId": "bank-acc-7890",
"data": {
"firstName": "María",
"lastName": "González",
"email": "[email protected]",
"countryCode": "CO"
},
"complianceUrl": "https://in.sumsub.com/websdk/p/AbCd1234EfGh5678"
},
"createdAt": "2025-01-15T09:15:00.000Z",
"updatedAt": "2025-01-15T09:45:00.000Z",
"attempts": 0
}
处理账户事件:
if (webhookEvent.event === 'ACCOUNT' && webhookEvent.action === 'UPDATE') {
const { status, type } = webhookEvent.data;
if (status === 'ACTIVE') {
console.log(`${type} 账户已验证并准备就绪`);
await notifyAccountReady(webhookEvent.data.id);
}
if (status === 'REJECTED') {
console.log('账户验证失败');
await requestNewAccountInfo(webhookEvent.data.id);
}
}
账户类型:
PSE - 哥伦比亚银行
SPEI - 墨西哥银行
ACH - 美国银行
WIRE - 电汇
WALLET - 加密货币钱包
PIX - 巴西 PIX
TRANSFIYA - 哥伦比亚移动钱包
账户状态:
PENDING - 等待验证
ACTIVE - 已验证
REJECTED - 验证失败
INACTIVE - 已禁用
TRANSACTION 事件
类型: TRANSACTION
操作:
CREATE - 交易已启动
UPDATE - 交易状态已更改
DELETE - 交易已取消(罕见)
示例 - 储蓄存款:
{
"id": "evt_1a2b3c4d-5e6f-7890-abcd-ef1234567890",
"event": "TRANSACTION",
"action": "UPDATE",
"data": {
"id": "txn_9876543210abcdef",
"userId": "e3d5c4ca-839a-4067-af76-89b33b19696e",
"savingsAccountId": "sav_1234567890abcdef",
"type": "DEPOSIT",
"status": "COMPLETED",
"amount": "1000.00",
"currency": "USD",
"method": "ACH"
},
"createdAt": "2025-01-15T10:30:00.000Z",
"updatedAt": "2025-01-15T10:35:00.000Z",
"attempts": 0
}
交易类型:
DEPOSIT - 储蓄存款
WITHDRAWAL - 储蓄提款
INTEREST - 利息支付
FEE - 费用收取
CUSTODIAL_ACCOUNT 事件
类型: CUSTODIAL_ACCOUNT
操作:
CREATE - 账户已创建
UPDATE - 余额或状态已更新
DELETE - 账户已关闭(罕见)
示例 - 余额已更新:
{
"id": "evt_abcd1234-ef56-7890-1234-567890abcdef",
"event": "CUSTODIAL_ACCOUNT",
"action": "UPDATE",
"data": {
"id": "cust_1234567890abcdef",
"userId": "e3d5c4ca-839a-4067-af76-89b33b19696e",
"type": "SAVINGS",
"status": "ACTIVE",
"balance": "5250.00",
"currency": "USD",
"previousBalance": "5000.00",
"changeAmount": "250.00",
"changeReason": "DEPOSIT",
"interestRate": "4.50"
},
"createdAt": "2025-01-15T10:35:00.000Z",
"updatedAt": "2025-01-15T10:35:00.000Z",
"attempts": 0
}
更改原因:
DEPOSIT - 已存入资金
WITHDRAWAL - 已提取资金
INTEREST - 已记入利息
FEE - 已收取费用
ADJUSTMENT - 手动调整
事件处理最佳实践
按事件和操作过滤
const handleWebhook = (webhookEvent) => {
const { event, action, data } = webhookEvent;
// 按事件类型路由
if (event === 'RAMP') {
if (action === 'CREATE') {
handleRampCreated(data);
} else if (action === 'UPDATE') {
handleRampUpdated(data);
}
} else if (event === 'USER') {
if (action === 'UPDATE') {
handleUserUpdated(data);
}
}
// ... 其他事件类型
};
检查特定状态
const handleRampUpdated = (data) => {
switch(data.status) {
case 'CASH_IN_COMPLETED':
console.log('支付已收到');
break;
case 'COMPLETED':
console.log('Ramp 已完成:', data.transferProof);
updateDatabase(data);
notifyUser(data.userId);
break;
case 'FAILED':
console.log('Ramp 失败:', data.details);
handleFailure(data);
break;
}
};
使用事件 ID 实现幂等性
const processWebhook = async (webhookEvent) => {
// 检查是否已处理
const existing = await db.webhookEvents.findOne({
eventId: webhookEvent.id
});
if (existing) {
console.log('重复事件,跳过');
return { received: true, duplicate: true };
}
// 存储并处理
await db.webhookEvents.create({
eventId: webhookEvent.id,
eventType: webhookEvent.event,
action: webhookEvent.action,
payload: webhookEvent.data,
attempts: webhookEvent.attempts,
receivedAt: new Date()
});
await handleWebhook(webhookEvent);
return { received: true };
};
监控重试尝试
if (webhookEvent.attempts > 3) {
console.warn(`事件 ${webhookEvent.id} 的重试次数较高`);
// 提醒监控系统
alertOps('webhook-retries-high', {
eventId: webhookEvent.id,
attempts: webhookEvent.attempts,
event: webhookEvent.event
});
}
快速参考
| 事件类型 | 常见操作 | 关键数据字段 |
|---|
| RAMP | CREATE, UPDATE | status, transferProof, fromAmount, toAmount |
| USER | CREATE, UPDATE | status, accessLevel, data |
| ACCOUNT | CREATE, UPDATE, DELETE | status, type, data |
| TRANSACTION | CREATE, UPDATE | type, status, amount |
| CUSTODIAL_ACCOUNT | UPDATE | balance, changeReason, changeAmount |
下一步