Saltar al contenido

Lección 19 de 25 · Lectura y práctica

Práctica: Implementar un Endpoint de Webhook Seguro

Concepto claveLos webhooks de Stripe son notificaciones HTTP que Stripe envía a tu servidor cuando ocurren eventos importantes en tu cuenta, como un pago exitoso, una suscripción cancelada o una factura generada. Imagina que son.

Del concepto a la práctica

Taller de esta lección

Un endpoint seguro combina cuatro controles: cuerpo original, firma válida, tipos permitidos e idempotencia. La respuesta rápida no debe sacrificar el registro de errores ni ejecutar acciones no autorizadas.

Ejemplo resuelto

El endpoint acepta solo invoice.payment_succeeded y customer.subscription.updated. Para un evento válido, comprueba event.id, guarda la recepción durable y confirma el estado done junto con la actualización local en una transacción. Para uno no permitido, registra el tipo y no cambia datos.

Una firma inválida obtiene 400 y cero cambios. Un tipo válido pero no utilizado puede obtener 2xx sin efectos. Si no pudiste almacenar la entrega, responde 5xx para permitir reintento; si la aceptaste duramente en cola, devuelve 2xx y procesa después. En entorno externo utiliza HTTPS y limita el endpoint a su función. No devuelvas 200 tras descartar por error un evento requerido.

Pruébalo

  1. Envía un evento válido firmado.

  2. Envía el mismo evento dos veces.

  3. Envía una firma inválida.

  4. Envía un tipo no permitido.

Error frecuente: marcar done antes de una acción que falla, o ejecutar efectos externos sin recuperar el estado. Usa una transacción local para efectos persistentes y outbox para tareas externas; processing necesita plazo de bloqueo y recuperación.

Comprueba tu resultado

Debe haber un cambio para el primer evento válido, ningún cambio adicional para el duplicado, rechazo para firma inválida y registro sin efectos para el tipo no permitido.

Proveedor de pago que envía un webhook verificado al servidor; registro idempotente antes de conceder acceso.
CursaloMapa de práctica
Confirmar el pago en el servidor

El servidor concede acceso a partir de un evento verificado del proveedor y un registro persistente. La pantalla de éxito del navegador es una notificación, no una prueba suficiente de pago. La idempotencia evita duplicar la operación cuando el evento llega más de una vez.

Ver la infografía en tamaño completo ↗
Fuentes para profundizar

Concepto clave

Los webhooks de Stripe son notificaciones HTTP que Stripe envía a tu servidor cuando ocurren eventos importantes en tu cuenta, como un pago exitoso, una suscripción cancelada o una factura generada. Imagina que son como mensajeros que llegan a tu puerta cada vez que algo relevante sucede en el sistema de pagos, permitiéndote reaccionar en tiempo real sin tener que consultar constantemente la API.

La seguridad es crítica porque estos webhooks contienen datos sensibles sobre transacciones y clientes. Un endpoint inseguro podría permitir que atacantes falsifiquen eventos o accedan a información confidencial. En la práctica, esto se protege verificando la firma digital que Stripe incluye en cada solicitud, similar a cómo un sello oficial autentica un documento importante.

Cómo funciona en la práctica

Cuando configuras un webhook en el dashboard de Stripe, proporcionas una URL de tu servidor (por ejemplo, https://tudominio.com/api/webhooks/stripe). Stripe enviará una solicitud POST a esta URL cada vez que ocurra un evento al que te hayas suscrito. Tu servidor debe:

  1. Recibir la solicitud y extraer el cuerpo (payload) y los encabezados
  2. Verificar la firma usando tu clave secreta de webhook
  3. Procesar el evento según su tipo
  4. Responder con un código de estado HTTP 200 para confirmar la recepción

Un flujo típico para una suscripción sería: 1) Cliente completa el pago inicial, 2) Stripe envía evento customer.subscription.created, 3) Tu endpoint recibe el evento, 4) Actualiza tu base de datos para activar el acceso del cliente.

Código en acción

Aquí tienes un ejemplo básico en Node.js usando Express:

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

// Middleware para parsear JSON
app.use(express.json());

app.post('/api/webhooks/stripe', async (req, res) => {
  const sig = req.headers['stripe-signature'];
  
  try {
    // Verificar la firma del webhook
    const event = stripe.webhooks.constructEvent(
      req.body,
      sig,
      process.env.STRIPE_WEBHOOK_SECRET
    );
    
    // Manejar diferentes tipos de eventos
    switch (event.type) {
      case 'customer.subscription.created':
        await handleSubscriptionCreated(event.data.object);
        break;
      case 'invoice.payment_succeeded':
        await handlePaymentSucceeded(event.data.object);
        break;
      case 'customer.subscription.deleted':
        await handleSubscriptionDeleted(event.data.object);
        break;
      default:
        console.log(`Evento no manejado: ${event.type}`);
    }
    
    res.json({received: true});
  } catch (err) {
    console.error('Error en webhook:', err.message);
    res.status(400).send(`Webhook Error: ${err.message}`);
  }
});

async function handleSubscriptionCreated(subscription) {
  // Aquí actualizarías tu base de datos
  console.log(`Suscripción creada: ${subscription.id}`);
  // Ejemplo: activar acceso del usuario en tu sistema
}

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Servidor escuchando en puerto ${PORT}`);
});

Y aquí una versión mejorada con manejo de errores más robusto:

// Versión mejorada con retry logic y logging
app.post('/api/webhooks/stripe', async (req, res) => {
  const sig = req.headers['stripe-signature'];
  const eventId = req.headers['stripe-event-id'] || 'unknown';
  
  // Log inicial para debugging
  console.log(`Recibiendo webhook ${eventId}`);
  
  try {
    const event = stripe.webhooks.constructEvent(
      req.rawBody || JSON.stringify(req.body),
      sig,
      process.env.STRIPE_WEBHOOK_SECRET
    );
    
    // Verificar que no hayamos procesado este evento antes
    if (await isEventProcessed(event.id)) {
      console.log(`Evento ${event.id} ya procesado, ignorando`);
      return res.json({received: true});
    }
    
    // Procesar en background para responder rápido a Stripe
    processEventAsync(event);
    
    // Marcar como procesado
    await markEventAsProcessed(event.id);
    
    res.json({received: true});
  } catch (err) {
    // Log detallado del error
    console.error(`Error en webhook ${eventId}:`, {
      error: err.message,
      signature: sig,
      body: req.body
    });
    
    // Diferenciar entre errores de firma y otros errores
    if (err.message.includes('Signature')) {
      res.status(401).send('Firma inválida');
    } else {
      res.status(400).send(`Error: ${err.message}`);
    }
  }
});

Errores comunes

  • No verificar la firma del webhook: Esto expone tu endpoint a ataques de falsificación. Siempre usa stripe.webhooks.constructEvent() o su equivalente en tu lenguaje.
  • Usar el mismo secret para diferentes entornos: Genera secrets separados para desarrollo, staging y producción en el dashboard de Stripe.
  • No manejar eventos duplicados: Stripe puede reenviar eventos. Implementa idempotencia almacenando IDs de eventos procesados.
  • Procesamiento síncrono largo: Si tu lógica tarda más de 10 segundos, Stripe podría reintentar. Procesa en background y responde inmediatamente.
  • No probar con eventos reales: Usa la CLI de Stripe o el modo test para enviar eventos de prueba antes de ir a producción.

Checklist de dominio

  1. ✅ Configuré un endpoint webhook en el dashboard de Stripe con la URL correcta
  2. ✅ Implementé verificación de firma usando el webhook secret
  3. ✅ Manejo al menos 3 tipos de eventos relevantes para mi negocio
  4. ✅ Mi endpoint responde en menos de 5 segundos para evitar timeouts
  5. ✅ Implementé idempotencia para evitar procesamiento duplicado
  6. ✅ Tengo logging adecuado para debugging de eventos
  7. ✅ Probé mi endpoint con la CLI de Stripe y eventos reales

Implementa un endpoint de webhook seguro para manejar suscripciones

En este ejercicio, crearás un endpoint de webhook seguro que maneje eventos críticos de suscripciones. Sigue estos pasos:

  1. Configura tu entorno: Crea un proyecto Node.js/Express básico e instala el SDK de Stripe (npm install stripe express). Configura las variables de entorno para STRIPE_SECRET_KEY y STRIPE_WEBHOOK_SECRET.
  2. Crea el endpoint: Implementa una ruta POST en /api/webhooks/stripe que:
    • Reciba solicitudes POST con JSON
    • Extraiga el header stripe-signature
    • Verifique la firma usando stripe.webhooks.constructEvent()
    • Responda con 200 si es válido, 401 si la firma es inválida
  3. Implementa manejo de eventos: Añade lógica para procesar estos eventos:
    • customer.subscription.created: Registrar en consola "Suscripción creada" + ID
    • invoice.payment_succeeded: Registrar "Pago exitoso" + monto
    • customer.subscription.deleted: Registrar "Suscripción cancelada" + razón
  4. Añade idempotencia: Implementa un sistema simple (puede ser en memoria) para evitar procesar el mismo evento dos veces.
  5. Prueba con la CLI: Instala la CLI de Stripe y ejecuta stripe listen --forward-to localhost:3000/api/webhooks/stripe para probar eventos reales.

Entrega: Código completo del endpoint y captura de pantalla mostrando eventos procesados correctamente.

Pistas
  • Recuerda que req.body debe ser el string RAW, no el objeto parseado, para la verificación de firma
  • Puedes usar un Set o un objeto simple en memoria para almacenar IDs de eventos procesados temporalmente
  • La CLI de Stripe necesita autenticación con stripe login antes de poder usarla

Evalua tu comprension

Completa el quiz interactivo de arriba para ganar XP.

Sigue con Implementación Profesional de Suscripciones y Facturación con Stripe

Esta lección forma parte de un curso de Cursalo. Revisa su programa y elige entre la compra individual o el acceso a toda la escuela.

Si ya tenías acceso a este curso, entra con tu cuenta: tus derechos y tu progreso se conservan. No necesitas comprarlo de nuevo.

01 / ELEGIR UNA RUTA

El próximo paso

Aprende IA mientras construyes algo que sí necesitas.

Empieza por el curso inicial o explora el catálogo para encontrar una práctica alineada con tu trabajo.