Estructura del evento
Todos los eventos de webhook siguen esta estructura:
{
id : string , // UUID - ID único del evento
event : string , // Tipo de evento: RAMP | USER | TRANSACTION | ACCOUNT | CUSTODIAL_ACCOUNT
action : string , // Acción: CREATE | UPDATE | DELETE
data : object , // Datos específicos del evento
createdAt : string , // Timestamp ISO 8601
updatedAt : string , // Timestamp ISO 8601
attempts : number // Conteo de intentos de entrega (0 a 5)
}
Usa el campo id para idempotencia. El campo attempts indica el conteo de reintentos.
Tipos de evento
Eventos RAMP
Tipo: RAMP
Acciones:
CREATE - Ramp creado
UPDATE - Estado del ramp cambió
DELETE - Ramp cancelado (raro)
Ejemplo - Ramp creado:
{
"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
}
Ejemplo - Ramp completado:
{
"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
}
Cómo manejar eventos RAMP:
if ( webhookEvent . event === 'RAMP' && webhookEvent . action === 'UPDATE' ) {
const { status , transferProof } = webhookEvent . data ;
switch ( status ) {
case 'CASH_IN_COMPLETED' :
console . log ( 'Payment received' );
break ;
case 'COMPLETED' :
console . log ( 'Ramp completed:' , transferProof );
await notifyUser ( webhookEvent . data . userId );
break ;
case 'FAILED' :
console . log ( 'Ramp failed' );
await handleFailure ( webhookEvent . data );
break ;
}
}
Estados comunes:
CREATED - Ramp inicializado
CASH_IN_PROCESSING - Pago siendo procesado
CASH_IN_COMPLETED - Pago confirmado
CONVERSION_PROCESSING - Convirtiendo moneda
CONVERSION_COMPLETED - Conversión completada
CASH_OUT_PROCESSING - Enviando fondos
COMPLETED - Totalmente completado
FAILED - Falló
CANCELED - Cancelado
Eventos USER
Tipo: USER
Acciones:
CREATE - Usuario registrado
UPDATE - Información o estado del usuario cambió
DELETE - Usuario eliminado (raro)
Ejemplo - KYC aprobado:
{
"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
}
Cómo manejar eventos USER:
if ( webhookEvent . event === 'USER' && webhookEvent . action === 'UPDATE' ) {
const { status , accessLevel } = webhookEvent . data ;
if ( status === 'ACTIVE' && accessLevel !== 'L0' ) {
// KYC aprobado - habilitar funcionalidades
await enableUserFeatures ( webhookEvent . data . id , accessLevel );
}
if ( status === 'REJECTED' ) {
// KYC rechazado - notificar al usuario
await sendKYCRejectionEmail ( webhookEvent . data );
}
}
Estados del usuario:
PENDING - Esperando verificación
ACTIVE - Verificado y activo
REJECTED - KYC rechazado
SUSPENDED - Cuenta suspendida
Niveles de acceso:
L0 - No verificado
L1 - Básico ($1K diario)
L2 - Estándar ($10K diario)
L3 - Mejorado ($50K diario)
L4 - Premium (Ilimitado)
Eventos ACCOUNT
Tipo: ACCOUNT
Acciones:
CREATE - Cuenta agregada
UPDATE - Estado de la cuenta cambió
DELETE - Cuenta eliminada
Ejemplo - Cuenta verificada:
{
"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
}
Cómo manejar eventos ACCOUNT:
if ( webhookEvent . event === 'ACCOUNT' && webhookEvent . action === 'UPDATE' ) {
const { status , type } = webhookEvent . data ;
if ( status === 'ACTIVE' ) {
console . log ( ` ${ type } account verified and ready` );
await notifyAccountReady ( webhookEvent . data . id );
}
if ( status === 'REJECTED' ) {
console . log ( 'Account verification failed' );
await requestNewAccountInfo ( webhookEvent . data . id );
}
}
Tipos de cuenta:
PSE - Banco colombiano
SPEI - Banco mexicano
ACH - Banco en EE. UU.
WIRE - Transferencia bancaria
WALLET - Wallet cripto
PIX - PIX de Brasil
TRANSFIYA - Wallet móvil en Colombia
Estados de cuenta:
PENDING - Esperando verificación
ACTIVE - Verificada
REJECTED - Verificación fallida
INACTIVE - Deshabilitada
Eventos TRANSACTION
Tipo: TRANSACTION
Acciones:
CREATE - Transacción iniciada
UPDATE - Estado de la transacción cambió
DELETE - Transacción cancelada (raro)
Ejemplo - Depósito en ahorros:
{
"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
}
Tipos de transacción:
DEPOSIT - Depósito en ahorros
WITHDRAWAL - Retiro desde ahorros
INTEREST - Pago de intereses
FEE - Cobro de comisión
Eventos CUSTODIAL_ACCOUNT
Tipo: CUSTODIAL_ACCOUNT
Acciones:
CREATE - Cuenta creada
UPDATE - Balance o estado actualizado
DELETE - Cuenta cerrada (raro)
Ejemplo - Balance actualizado:
{
"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
}
Motivos de cambio:
DEPOSIT - Fondos depositados
WITHDRAWAL - Fondos retirados
INTEREST - Interés acreditado
FEE - Comisión cobrada
ADJUSTMENT - Ajuste manual
Mejores prácticas para el manejo de eventos
Filtra por evento y acción
const handleWebhook = ( webhookEvent ) => {
const { event , action , data } = webhookEvent ;
// Enrutar por tipo de evento
if ( event === 'RAMP' ) {
if ( action === 'CREATE' ) {
handleRampCreated ( data );
} else if ( action === 'UPDATE' ) {
handleRampUpdated ( data );
}
} else if ( event === 'USER' ) {
if ( action === 'UPDATE' ) {
handleUserUpdated ( data );
}
}
// ... otros tipos de eventos
};
Valida el estado específico
const handleRampUpdated = ( data ) => {
switch ( data . status ) {
case 'CASH_IN_COMPLETED' :
console . log ( 'Payment received' );
break ;
case 'COMPLETED' :
console . log ( 'Ramp completed:' , data . transferProof );
updateDatabase ( data );
notifyUser ( data . userId );
break ;
case 'FAILED' :
console . log ( 'Ramp failed:' , data . details );
handleFailure ( data );
break ;
}
};
Usa el ID del evento para idempotencia
const processWebhook = async ( webhookEvent ) => {
// Verificar si ya fue procesado
const existing = await db . webhookEvents . findOne ({
eventId: webhookEvent . id
});
if ( existing ) {
console . log ( 'Duplicate event, skipping' );
return { received: true , duplicate: true };
}
// Guardar y procesar
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 };
};
Monitorea los reintentos
if ( webhookEvent . attempts > 3 ) {
console . warn ( `High retry count for event ${ webhookEvent . id } ` );
// Alertar al sistema de monitoreo
alertOps ( 'webhook-retries-high' , {
eventId: webhookEvent . id ,
attempts: webhookEvent . attempts ,
event: webhookEvent . event
});
}
Referencia rápida
Tipo de evento Acciones comunes Campos clave en data 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
Próximos pasos
Configuración de Webhook Configura endpoints de webhook
Guía de Seguridad Verifica firmas de webhook
Concepto de Webhook Aprende sobre la arquitectura de webhooks
Referencia API Documentación de la API de Webhooks