Skip to main content

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

  1. Accede al panel de Cobrix
  2. Ve a ConfiguraciónWebhooks
  3. 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"
    }
  }
}
CampoDescripción
idID único del evento (para idempotencia)
eventTipo de evento
created_atTimestamp ISO 8601
dataDatos específicos del evento

Eventos Disponibles

Checkout

EventoDescripción
checkout.session.createdSesión creada
checkout.session.completedCheckout completado
checkout.session.expiredSesión expiró

Pagos

EventoDescripción
payment.processingPago en proceso
payment.succeeded✅ Pago exitoso
payment.failed❌ Pago fallido
payment.refundedPago reembolsado

Verificación de Firma

CRÍTICO: Siempre verifica la firma antes de procesar un webhook.

Headers

HeaderDescripción
X-Cobrix-SignatureFirma HMAC
X-Cobrix-TimestampUnix timestamp

Formato

X-Cobrix-Signature: t=1706023800,v1=5257a869e7ecebeda32affa62cdca3fa...

Algoritmo

1

Extraer valores

Parsear t (timestamp) y v1 (firma) del header
2

Construir payload

Concatenar: {timestamp}.{raw_body}
3

Calcular HMAC

HMAC-SHA256 con tu webhook secret
4

Comparar

Comparación timing-safe con la firma recibida
5

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á:
IntentoDelay
1Inmediato
21 minuto
35 minutos
430 minutos
52 horas
624 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