¿Qué son los Webhooks?
Webhooks son callbacks HTTP que KillB envía a tu servidor cuando ocurren eventos. Habilitan notificaciones en tiempo real sobre ramps, usuarios, cuentas y transacciones sin polling constante.
Los webhooks son la forma recomendada de monitorear el estado de transacciones y mantener tu aplicación sincronizada con KillB.
Cómo Funcionan los Webhooks
Estructura del Evento Webhook
Todos los eventos 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)
}
Eventos de Webhook
KillB soporta los siguientes tipos de eventos:
Eventos de transacción ramp Tipo de Evento: RAMPAcciones:
CREATE - Nuevo ramp creado
UPDATE - Estado del ramp cambió
DELETE - Ramp cancelado (raro)
Valores de Estado Comunes:
QUOTE_CREATED - Cotización creada
PENDING_PAYMENT - Esperando pago
CASH_IN_PROCESSING - Pago siendo procesado
CASH_IN_COMPLETED - Pago confirmado
PROCESSING - Convirtiendo moneda
CASH_OUT_PROCESSING - Enviando fondos
COMPLETED - Ramp completado
FAILED - Ramp falló
CANCELED - Ramp cancelado
Eventos de usuario y KYC Tipo de Evento: USERAcciones:
CREATE - Nuevo usuario registrado
UPDATE - Información del usuario o estado KYC cambió
DELETE - Usuario eliminado (raro)
Valores de Estado:
PENDING - Esperando verificación
ACTIVE - Verificado y activo
REJECTED - KYC rechazado
SUSPENDED - Cuenta suspendida
Niveles de Acceso:
L0 - No verificado
L1 - Verificación básica
L2 - Verificación estándar
L3 - Verificación mejorada
L4 - Verificación premium
Eventos de verificación de cuenta Tipo de Evento: ACCOUNTAcciones:
CREATE - Nueva cuenta agregada
UPDATE - Estado o detalles de cuenta cambiaron
DELETE - Cuenta eliminada
Tipos de Cuenta:
PSE - Cuenta bancaria colombiana
SPEI - Cuenta bancaria mexicana
ACH - Cuenta bancaria de EE.UU.
WIRE - Cuenta de transferencia wire
WALLET - Billetera cripto
PIX - Cuenta PIX brasileña
TRANSFIYA - Billetera móvil colombiana
Valores de Estado:
PENDING - Esperando verificación
ACTIVE - Verificado y activo
REJECTED - Verificación falló
INACTIVE - Deshabilitado
Configurando Webhooks
Crear Configuración de Webhook
Solicitud:
{
"url" : "https://api.tuapp.com/webhooks/killb" ,
"secret" : "tu-clave-secreta-min-32-caracteres" ,
"events" : [ "RAMP" , "USER" , "ACCOUNT" ]
}
Respuesta:
{
"id" : "webhook-id" ,
"customerId" : "customer-id" ,
"url" : "https://api.tuapp.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.tuapp.com/webhooks/killb' ,
secret: process . env . WEBHOOK_SECRET ,
events: [ 'RAMP' , 'USER' , 'ACCOUNT' , 'TRANSACTION' ]
})
});
return await response . json ();
};
Seguridad de Webhook
Verificación de Firma
KillB firma todas las solicitudes webhook con HMAC SHA-256. Siempre verifica firmas para asegurar autenticidad.
Encabezado: 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 ();
// Calcular firma esperada
const expectedSignature = crypto
. createHmac ( 'sha256' , process . env . WEBHOOK_SECRET )
. update ( payload )
. digest ( 'hex' );
// Verificar firma
if ( signature !== expectedSignature ) {
console . error ( 'Firma de webhook inválida' );
return res . status ( 401 ). json ({ error: 'Firma inválida' });
}
// Analizar y procesar evento
const event = JSON . parse ( payload );
processWebhookEvent ( event );
res . status ( 200 ). json ({ received: true });
});
¡Nunca omitas la verificación de firma! Webhooks no verificados pueden ser falsificados por atacantes.
Requisitos de Webhook
Tu endpoint de webhook debe:
Retornar 200 OK dentro de 5 segundos
Procesar eventos asincrónicamente
No esperar por operaciones lentas
Usar colas de jobs para procesamiento pesado
Ser idempotente (manejar eventos duplicados)
Usar ID del evento para detectar duplicados
Almacenar IDs de eventos procesados
Saltar eventos ya procesados
Mantener >99% de uptime
Usar solo HTTPS
Tener certificado SSL válido
Responder con códigos de estado apropiados
Siempre verificar x-signature-sha256
Usar comparación de tiempo constante
Rechazar firmas inválidas
Registrar fallas de verificación
Lógica de Reintento
KillB automáticamente reintenta entregas de webhook fallidas con backoff exponencial:
Agenda de Reintento:
Intento 0: Entrega inmediata
Intento 1: Después de 1 minuto
Intento 2: Después de 5 minutos
Intento 3: Después de 15 minutos
Intento 4: Después de 1 hora
Intento 5: Después de 6 horas (intento final)
Criterios de Falla:
Código de respuesta no-2xx
Timeout de conexión (> 5 segundos)
Error de red
El campo attempts en el payload del webhook indica el intento de entrega actual (0 a 5).
Después de que el intento 5 falla, el webhook se marca como permanentemente fallido y requiere intervención manual. Verifica tus logs de webhook y corrige cualquier problema.
Probando Webhooks
Prueba Local con ngrok
# Instalar ngrok
npm install -g ngrok
# Iniciar tu servidor local
node server.js
# Exponer a internet
ngrok http 3000
# Usar URL ngrok en configuración de webhook
# https://abc123.ngrok.io/webhooks/killb
Prueba Manual
Activa eventos de prueba en sandbox:
// Crear ramp de prueba
const ramp = await createRamp ( quoteId , userId , accountId );
// Simular finalización (activa webhooks)
await fetch ( '/api/v2/faker/cash-in' , {
method: 'POST' ,
headers: { 'Authorization' : `Bearer ${ token } ` },
body: JSON . stringify ({ rampId: ramp . id })
});
// Tu webhook debe recibir evento ramp.cash_in_completed
Procesamiento de Eventos
Ejemplo de Handler
const processWebhookEvent = ( webhookEvent ) => {
const { id , event , action , data , attempts } = webhookEvent ;
console . log ( `Procesando ${ event } . ${ action } (intento ${ attempts } )` );
// Enrutar basado en tipo de evento y acción
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 ;
default :
console . log ( `Tipo de evento no manejado: ${ event } ` );
}
};
const handleRampEvent = async ( action , data ) => {
if ( action === 'UPDATE' && data . status === 'COMPLETED' ) {
// Actualizar base de datos
await db . ramps . update ( data . id , {
status: data . status ,
updatedAt: data . updatedAt ,
transferProof: data . transferProof
});
// Notificar usuario
await sendEmail ( data . userId , 'Transacción Completada' , {
amount: data . toAmount ,
currency: data . toCurrency ,
txHash: data . transferProof
});
}
};
Idempotencia
Maneja entregas de webhook duplicadas usando el ID del evento:
const handleWebhook = async ( webhookEvent ) => {
const { id , event , action , data } = webhookEvent ;
// Verificar si ya procesado (usando ID único del evento)
const existing = await db . webhookEvents . findOne ({ eventId: id });
if ( existing ) {
console . log ( `Evento duplicado ${ id } , saltando` );
return { received: true , duplicate: true };
}
// Almacenar evento inmediatamente para prevenir procesamiento duplicado
await db . webhookEvents . create ({
eventId: id ,
eventType: event ,
action: action ,
payload: data ,
receivedAt: new Date (),
processed: false
});
try {
// Procesar evento
await processWebhookEvent ( webhookEvent );
// Marcar como procesado
await db . webhookEvents . update (
{ eventId: id },
{ processed: true , processedAt: new Date () }
);
return { received: true };
} catch ( error ) {
// Registrar error pero aún marcar como recibido
await db . webhookEvents . update (
{ eventId: id },
{ error: error . message , failedAt: new Date () }
);
throw error ;
}
};
Mejores Prácticas
app . post ( '/webhooks/killb' , async ( req , res ) => {
// Verificar firma
verifySignature ( req );
// Reconocer inmediatamente
res . status ( 200 ). json ({ received: true });
// Procesar asincrónicamente
processAsync ( req . body );
});
Retornar 200 OK inmediatamente
Procesar eventos en segundo plano
Usar colas de jobs (Bull, Celery, etc.)
No esperar por servicios externos
Manejar Fallas Graciosamente
const processWebhook = async ( event ) => {
try {
await processEvent ( event );
} catch ( error ) {
console . error ( 'Procesamiento de webhook falló:' , error );
// Registrar en servicio de monitoreo
await logError ( error , event );
// No lanzar - ya procesamos
// KillB reintentará de todos modos
}
};
Capturar todos los errores
Registrar fallas
No lanzar después de reconocer
Monitorear tasas de error
Almacenar Historial de Eventos
await db . webhookLogs . create ({
eventId: event . id ,
eventType: event . event ,
payload: event . data ,
receivedAt: new Date (),
processed: true
});
Mantener pista de auditoría
Habilitar replay si es necesario
Depurar problemas fácilmente
Cumplir requisitos de compliance
Próximos Pasos