Configuración de Webhooks

Los webhooks te permiten recibir notificaciones en tiempo real sobre eventos de pago desde Bloque.

¿Qué son los Webhooks?

Los webhooks son callbacks HTTP que notifican a tu servidor cuando ocurren eventos específicos, como pagos exitosos, transacciones fallidas o actualizaciones de estado.

Configurando Webhooks

1. Crear Endpoint de Webhook

Crea un endpoint en tu backend para recibir eventos de webhook:

import { Bloque } from '@bloque/payments';
import express from 'express';

const app = express();

// Inicializar SDK con secreto de webhook
const bloque = new Bloque({
  apiKey: process.env.BLOQUE_API_KEY!,
  mode: 'production',
  webhookSecret: process.env.BLOQUE_WEBHOOK_SECRET,
});

// Endpoint de webhook
app.post('/webhooks/bloque', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-bloque-signature'] as string;

  try {
    // Verificar firma del webhook
    const isValid = bloque.webhooks.verify(req.body, signature);

    if (!isValid) {
      return res.status(400).send('Firma inválida');
    }

    // Parsear evento
    const event = JSON.parse(req.body.toString());

    // Manejar evento
    handleWebhookEvent(event);

    res.json({ received: true });
  } catch (error) {
    console.error('Error de webhook:', error);
    res.status(400).send('Error de webhook');
  }
});

app.listen(3000);

2. Verificar Firmas de Webhook

Siempre verifica las firmas de webhook para asegurar que provienen de Bloque:

const isValid = bloque.webhooks.verify(
  requestBody,
  signatureHeader,
  { secret: process.env.BLOQUE_WEBHOOK_SECRET }
);

if (!isValid) {
  // Rechazar webhook
  return res.status(400).send('Firma inválida');
}

3. Manejar Eventos de Webhook

Procesa diferentes tipos de eventos:

function handleWebhookEvent(event) {
  switch (event.type) {
    case 'payment.succeeded':
      handlePaymentSucceeded(event.data);
      break;

    case 'payment.failed':
      handlePaymentFailed(event.data);
      break;

    case 'checkout.completed':
      handleCheckoutCompleted(event.data);
      break;

    case 'checkout.expired':
      handleCheckoutExpired(event.data);
      break;

    default:
      console.log('Tipo de evento no manejado:', event.type);
  }
}

Eventos de Webhook

payment.succeeded

Se dispara cuando un pago se procesa exitosamente.

{
  type: 'payment.succeeded',
  data: {
    id: 'pay_123',
    status: 'succeeded',
    amount: 2999,
    currency: 'USD',
    payment_method: 'card',
    created_at: '2024-01-15T10:30:00Z'
  }
}

payment.failed

Se dispara cuando un pago falla.

{
  type: 'payment.failed',
  data: {
    id: 'pay_123',
    status: 'failed',
    error: 'insufficient_funds',
    error_message: 'Fondos insuficientes'
  }
}

checkout.completed

Se dispara cuando una sesión de checkout se completa.

{
  type: 'checkout.completed',
  data: {
    id: 'checkout_123',
    status: 'completed',
    payment_id: 'pay_123'
  }
}

checkout.expired

Se dispara cuando una sesión de checkout expira.

{
  type: 'checkout.expired',
  data: {
    id: 'checkout_123',
    status: 'expired'
  }
}

Ejemplo Completo

import { Bloque } from '@bloque/payments';
import express from 'express';

const app = express();

const bloque = new Bloque({
  apiKey: process.env.BLOQUE_API_KEY!,
  mode: 'production',
  webhookSecret: process.env.BLOQUE_WEBHOOK_SECRET,
});

app.post('/webhooks/bloque', express.raw({ type: 'application/json' }), async (req, res) => {
  const signature = req.headers['x-bloque-signature'] as string;

  try {
    // Verificar firma
    const isValid = bloque.webhooks.verify(req.body, signature);

    if (!isValid) {
      return res.status(400).send('Firma inválida');
    }

    // Parsear evento
    const event = JSON.parse(req.body.toString());
    console.log('Webhook recibido:', event.type);

    // Manejar eventos
    switch (event.type) {
      case 'payment.succeeded':
        // Actualizar estado de orden
        await updateOrderStatus(event.data.id, 'paid');
        // Enviar email de confirmación
        await sendConfirmationEmail(event.data);
        break;

      case 'payment.failed':
        // Registrar fallo
        console.error('Pago fallido:', event.data);
        // Notificar al cliente
        await sendFailureNotification(event.data);
        break;

      case 'checkout.completed':
        // Marcar checkout como completo
        await markCheckoutComplete(event.data.id);
        break;
    }

    res.json({ received: true });
  } catch (error) {
    console.error('Error de webhook:', error);
    res.status(400).send('Error de webhook');
  }
});

app.listen(3000);

Mejores Prácticas

1. Responder Rápidamente

Siempre responde con 200 OK rápidamente. Procesa eventos de forma asíncrona:

app.post('/webhooks/bloque', async (req, res) => {
  const signature = req.headers['x-bloque-signature'] as string;

  // Verificar
  const isValid = bloque.webhooks.verify(req.body, signature);
  if (!isValid) {
    return res.status(400).send('Firma inválida');
  }

  // Responder inmediatamente
  res.json({ received: true });

  // Procesar de forma asíncrona
  const event = JSON.parse(req.body.toString());
  processWebhookAsync(event).catch(console.error);
});

2. Manejar Idempotencia

Los webhooks pueden enviarse múltiples veces. Almacena IDs de eventos procesados:

const processedEvents = new Set();

function handleWebhookEvent(event) {
  if (processedEvents.has(event.id)) {
    console.log('Evento ya procesado:', event.id);
    return;
  }

  // Procesar evento
  processEvent(event);

  // Marcar como procesado
  processedEvents.add(event.id);
}

3. Lógica de Reintento

Bloque reintentará webhooks si tu endpoint falla. Asegura que tu endpoint sea idempotente.

4. Asegurar tu Endpoint

  • Siempre verifica firmas de webhook
  • Usa HTTPS en producción
  • Mantén los secretos de webhook seguros
  • Limita la tasa de requests a endpoints de webhook

Probando Webhooks

Prueba webhooks localmente usando herramientas como ngrok:

# Iniciar ngrok
ngrok http 3000

# Usa la URL de ngrok en el dashboard de Bloque
https://tu-url-ngrok.ngrok.io/webhooks/bloque

Próximos Pasos