Descripción General
Los webhooks de Cobrix te permiten recibir notificaciones en tiempo real cuando ocurren eventos importantes como pagos completados.
Configuración
1. Crear Webhook
- Accede al panel de Cobrix
- Ve a Configuración → Webhooks
- Crea un nuevo webhook con tu URL
2. Guardar el Secret
Al crear el webhook, recibirás un signing secret:
whsec_abc123def456ghi789jkl012mno345pqr678stu901vwx234yz567
Guarda este secret de forma segura. Solo se muestra una vez.
# .env
COBRIX_WEBHOOK_SECRET=whsec_abc123def456ghi789...
Estructura del Payload
{
"id": "evt_1234567890abcdef",
"event": "payment.succeeded",
"created_at": "2026-01-22T15:30:00.000Z",
"api_version": "2025-01-21",
"data": {
"payment": {
"transactionId": "txn_abc123",
"checkoutSessionId": "cs_xyz789",
"status": "succeeded",
"method": "debito_inmediato",
"amountMinor": 5000,
"currency": "USD",
"paidAt": "2026-01-22T15:30:00.000Z"
},
"product": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"sku": "PROD-001",
"name": "Membresía Premium"
},
"customer": {
"id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"email": "[email protected]"
},
"metadata": {
"orderId": "ORD-12345"
}
}
}
| Campo | Descripción |
|---|
id | ID único del evento (para idempotencia) |
event | Tipo de evento |
created_at | Timestamp ISO 8601 |
data | Datos específicos del evento |
Eventos Disponibles
Checkout
| Evento | Descripción |
|---|
checkout.session.created | Sesión creada |
checkout.session.completed | Checkout completado |
checkout.session.expired | Sesión expiró |
Pagos
| Evento | Descripción |
|---|
payment.processing | Pago en proceso |
payment.succeeded | ✅ Pago exitoso |
payment.failed | ❌ Pago fallido |
payment.refunded | Pago reembolsado |
Verificación de Firma
CRÍTICO: Siempre verifica la firma antes de procesar un webhook.
| Header | Descripción |
|---|
X-Cobrix-Signature | Firma HMAC |
X-Cobrix-Timestamp | Unix timestamp |
X-Cobrix-Signature: t=1706023800,v1=5257a869e7ecebeda32affa62cdca3fa...
Algoritmo
Extraer valores
Parsear t (timestamp) y v1 (firma) del header
Construir payload
Concatenar: {timestamp}.{raw_body}
Calcular HMAC
HMAC-SHA256 con tu webhook secret
Comparar
Comparación timing-safe con la firma recibida
Validar timestamp
Verificar que no sea mayor a 5 minutos
Implementación
const crypto = require('crypto');
function verifyWebhookSignature(payload, signature, secret) {
if (!signature) return false;
// Extraer timestamp y firma
const parts = signature.split(',');
const timestampPart = parts.find(p => p.startsWith('t='));
const signaturePart = parts.find(p => p.startsWith('v1='));
if (!timestampPart || !signaturePart) return false;
const timestamp = parseInt(timestampPart.replace('t=', ''), 10);
const receivedSignature = signaturePart.replace('v1=', '');
// Verificar que no sea muy antiguo (5 minutos)
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - timestamp) > 300) {
console.error('Webhook timestamp demasiado antiguo');
return false;
}
// Calcular firma esperada
const signedPayload = `${timestamp}.${payload}`;
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest('hex');
// Comparación timing-safe
try {
return crypto.timingSafeEqual(
Buffer.from(receivedSignature),
Buffer.from(expectedSignature)
);
} catch {
return false;
}
}
Handler Completo
const express = require('express');
const router = express.Router();
router.post('/cobrix',
express.raw({ type: 'application/json' }),
async (req, res) => {
const signature = req.headers['x-cobrix-signature'];
const rawBody = req.body.toString();
// 1. Verificar firma
if (!verifyWebhookSignature(rawBody, signature, process.env.COBRIX_WEBHOOK_SECRET)) {
console.error('Firma inválida');
return res.status(401).json({ error: 'Invalid signature' });
}
const event = JSON.parse(rawBody);
console.log(`Webhook: ${event.event} (${event.id})`);
// 2. Responder inmediatamente
res.status(200).json({ received: true });
// 3. Procesar en background
setImmediate(async () => {
try {
switch (event.event) {
case 'payment.succeeded':
await handlePaymentSucceeded(event.data);
break;
case 'payment.failed':
await handlePaymentFailed(event.data);
break;
}
} catch (error) {
console.error('Error procesando webhook:', error);
}
});
}
);
async function handlePaymentSucceeded(data) {
const { payment, metadata } = data;
console.log(`✅ Pago exitoso: ${payment.transactionId}`);
// Actualizar orden en tu base de datos
if (metadata?.orderId) {
await orderService.markAsPaid(metadata.orderId, {
transactionId: payment.transactionId,
paidAt: payment.paidAt
});
}
}
Reintentos
Si tu endpoint no responde 2xx, Cobrix reintentará:
| Intento | Delay |
|---|
| 1 | Inmediato |
| 2 | 1 minuto |
| 3 | 5 minutos |
| 4 | 30 minutos |
| 5 | 2 horas |
| 6 | 24 horas |
Idempotencia
Usa el id del evento para evitar duplicados:
async function handleWebhook(event) {
// Verificar si ya procesamos
const existing = await db.webhookEvents.findUnique({
where: { eventId: event.id }
});
if (existing) {
console.log(`Evento ${event.id} ya procesado`);
return;
}
// Guardar antes de procesar
await db.webhookEvents.create({
data: { eventId: event.id, status: 'processing' }
});
// Procesar...
}
Mejores Prácticas
✅ Hacer
- Verificar firma siempre
- Responder 200 rápido
- Implementar idempotencia
- Usar HTTPS
- Loggear eventos
❌ No Hacer
- Confiar sin verificar firma
- Bloquear con operaciones lentas
- Exponer el secret
- Ignorar errores