Procesar Eventos de Suscripción y Facturación

Video
30 min~6 min lectura

Reproductor de video

Concepto clave

Los webhooks de Stripe son notificaciones HTTP que tu servidor recibe cuando ocurren eventos importantes en el sistema de pagos. Imagina que contratas un servicio de seguridad para tu casa: en lugar de revisar constantemente las cámaras (polling), el servicio te llama inmediatamente cuando detecta movimiento (webhook). En suscripciones y facturación, eventos como invoice.payment_succeeded o customer.subscription.updated son cruciales para mantener tu base de datos sincronizada y automatizar procesos.

Stripe envía estos eventos como peticiones POST a un endpoint que configuras, con un payload JSON que contiene toda la información relevante. Tu trabajo como backend developer es verificar que la petición sea auténtica (firmada por Stripe), procesar la información y responder rápidamente para evitar reintentos. Sin webhooks, tendrías que consultar constantemente la API de Stripe, lo que es ineficiente y puede hacerte perder eventos críticos.

Cómo funciona en la práctica

Vamos a desglosar el flujo completo para procesar un evento de factura pagada:

  1. Configuración en el Dashboard de Stripe: En la sección Developers > Webhooks, añades la URL de tu endpoint (ej: https://tudominio.com/api/stripe-webhook) y seleccionas los eventos a escuchar, como invoice.payment_succeeded.
  2. Recepción del evento: Cuando un cliente paga una factura, Stripe envía una petición POST a tu endpoint con un cuerpo JSON. Este incluye un ID único del evento, el tipo (type), y los datos (data.object) con detalles de la factura.
  3. Verificación de firma: Usas la clave secreta de webhook de Stripe para verificar que la petición es legítima, comparando la firma en el header Stripe-Signature.
  4. Procesamiento: Basado en el tipo de evento, actualizas tu base de datos. Por ejemplo, para invoice.payment_succeeded, marcas la factura como pagada y registras la transacción.
  5. Respuesta: Devuelves un código de estado HTTP 200 para confirmar la recepción. Si fallas (ej: error 500), Stripe reintentará el envío según su política.

Código en acción

Aquí tienes un ejemplo en Node.js con Express para manejar webhooks. Primero, instala stripe con npm install stripe.

const express = require('express');
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const app = express();

// Middleware para parsear el body como raw para verificación
app.post('/api/stripe-webhook', express.raw({type: 'application/json'}), async (req, res) => {
  const sig = req.headers['stripe-signature'];
  let event;

  try {
    // Verificar la firma usando la clave de webhook
    event = stripe.webhooks.constructEvent(
      req.body,
      sig,
      process.env.STRIPE_WEBHOOK_SECRET
    );
  } catch (err) {
    console.error(`Error de verificación: ${err.message}`);
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }

  // Manejar el tipo de evento
  switch (event.type) {
    case 'invoice.payment_succeeded':
      const invoice = event.data.object;
      console.log(`Factura ${invoice.id} pagada por ${invoice.customer}`);
      // Aquí actualizarías tu base de datos
      // Ej: marcar factura como pagada, enviar email de confirmación
      break;
    case 'customer.subscription.updated':
      const subscription = event.data.object;
      console.log(`Suscripción ${subscription.id} actualizada`);
      // Ej: sincronizar estado de suscripción en tu sistema
      break;
    default:
      console.log(`Evento no manejado: ${event.type}`);
  }

  res.json({received: true});
});

app.listen(3000, () => console.log('Servidor en puerto 3000'));

Ahora, mejoremos el código para manejar errores de base de datos y evitar procesamiento duplicado. Antes, podríamos procesar el mismo evento múltiples veces si hay reintentos. Después, añadimos un registro de eventos procesados.

// Mejora: Evitar duplicados con un registro simple
const processedEvents = new Set();

app.post('/api/stripe-webhook', express.raw({type: 'application/json'}), async (req, res) => {
  const sig = req.headers['stripe-signature'];
  let event;

  try {
    event = stripe.webhooks.constructEvent(req.body, sig, process.env.STRIPE_WEBHOOK_SECRET);
  } catch (err) {
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }

  // Verificar si el evento ya fue procesado
  if (processedEvents.has(event.id)) {
    console.log(`Evento ${event.id} ya procesado, ignorando`);
    return res.json({received: true});
  }

  try {
    switch (event.type) {
      case 'invoice.payment_succeeded':
        const invoice = event.data.object;
        // Lógica de negocio: actualizar base de datos
        await updateInvoiceInDB(invoice.id, { status: 'paid' });
        break;
      // Otros casos...
    }
    // Registrar evento como procesado
    processedEvents.add(event.id);
  } catch (dbError) {
    console.error(`Error en procesamiento: ${dbError.message}`);
    return res.status(500).send('Error interno');
  }

  res.json({received: true});
});

Errores comunes

  • No verificar la firma del webhook: Expones tu endpoint a ataques falsos. Siempre usa stripe.webhooks.constructEvent o equivalente en tu lenguaje.
  • Procesamiento lento o con timeouts: Si tu servidor tarda más de unos segundos en responder, Stripe puede reintentar, causando duplicados. Procesa de forma asíncrona si es necesario.
  • Olvidar manejar eventos inesperados: Stripe puede enviar eventos nuevos en actualizaciones. Registra los no manejados para ajustar luego.
  • No probar con eventos de test: Usa el modo test de Stripe y herramientas como el CLI para simular eventos antes de producción.
  • Almacenar la clave de webhook en código: Guárdala en variables de entorno para mayor seguridad.

Checklist de dominio

  1. Configuré un endpoint de webhook en Stripe y lo probé con eventos de test.
  2. Implementé verificación de firma en mi código backend.
  3. Manejo al menos 3 tipos de eventos de suscripción/facturación (ej: invoice.paid, subscription.updated, payment_failed).
  4. Mi endpoint responde con HTTP 200 dentro de 5 segundos para evitar reintentos.
  5. Tengo un registro para evitar procesamiento duplicado de eventos.
  6. Protegí las claves de webhook usando variables de entorno.
  7. Probé el flujo completo con una suscripción real en modo test.

Implementa un webhook para actualizar suscripciones en tu base de datos

En este ejercicio, crearás un endpoint de webhook que maneje eventos de suscripción de Stripe y actualice una base de datos simulada. Sigue estos pasos:

  1. Configura el entorno: Crea un proyecto Node.js con Express. Instala stripe usando npm install stripe express. Define variables de entorno para STRIPE_SECRET_KEY y STRIPE_WEBHOOK_SECRET (usa una clave de test desde el dashboard de Stripe).
  2. Crea el endpoint: Implementa una ruta POST en /webhook que use express.raw para parsear el body. Incluye la verificación de firma con stripe.webhooks.constructEvent.
  3. Maneja eventos clave: Detecta estos eventos: customer.subscription.created, customer.subscription.updated, y customer.subscription.deleted. Para cada uno, registra en consola el ID de suscripción y su nuevo estado (ej: active, canceled).
  4. Simula una base de datos: Usa un objeto en memoria (como un Map) para almacenar suscripciones con su ID y estado. Actualiza este objeto según los eventos.
  5. Prueba con Stripe CLI: Instala Stripe CLI y ejecuta stripe listen --forward-to localhost:3000/webhook. Luego, usa stripe trigger customer.subscription.created para enviar un evento de test y verifica que tu endpoint lo procese correctamente.
  6. Añade manejo de errores: Si la verificación falla, devuelve 400. Si hay error al actualizar la base de datos simulada, devuelve 500 y registra el error.

Entrega el código completo y capturas de pantalla de la consola mostrando los eventos procesados.

Pistas
  • Usa process.env para acceder a las claves de Stripe de forma segura.
  • Recuerda que el body debe ser raw para la verificación; no uses express.json() en esta ruta.
  • Puedes simular una base de datos con const subscriptions = new Map(); y métodos como set y get.

Evalua tu comprension

Completa el quiz interactivo de arriba para ganar XP.