Concepto clave
Los webhooks de Stripe son notificaciones HTTP que tu servidor recibe cuando ocurren eventos importantes en tu cuenta de Stripe. Imagina que son como mensajes de texto automáticos que Stripe te envía cuando algo relevante sucede, como cuando un pago se completa exitosamente. El evento payment_intent.succeeded es uno de los más críticos: indica que un cliente ha pagado correctamente y tú debes entregar el producto o servicio.
En el mundo real, esto es similar a cuando compras algo en línea y recibes un correo de confirmación automático después del pago. Stripe actúa como el sistema de notificaciones que desencadena acciones en tu backend, como actualizar la base de datos o enviar un email al cliente. Sin webhooks, tendrías que consultar constantemente a Stripe para ver si hubo cambios, lo que es ineficiente y poco confiable.
Cómo funciona en la práctica
Cuando un cliente completa un pago, Stripe genera un evento payment_intent.succeeded y lo envía a la URL de webhook que configuraste. Tu servidor debe:
- Recibir la solicitud HTTP POST con los datos del evento.
- Verificar la firma del webhook para asegurar que viene de Stripe.
- Procesar el evento según el tipo (en este caso, payment_intent.succeeded).
- Actualizar tu sistema, por ejemplo, marcando el pedido como pagado.
- Responder con un código HTTP 200 para confirmar la recepción.
Ejemplo de flujo: Un usuario paga una suscripción mensual de $10. Stripe crea un PaymentIntent, procesa el pago, y si es exitoso, dispara el webhook. Tu backend recibe el evento, extrae el ID del cliente y la cantidad, actualiza la base de datos para activar la suscripción, y envía un recibo por email.
Código en acción
Aquí tienes un ejemplo básico en Node.js usando Express y la librería oficial de Stripe:
const express = require('express');
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const app = express();
app.use(express.json());
app.post('/webhook', 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) {
console.error('Webhook signature verification failed:', err.message);
return res.status(400).send(`Webhook Error: ${err.message}`);
}
// Manejar el evento payment_intent.succeeded
if (event.type === 'payment_intent.succeeded') {
const paymentIntent = event.data.object;
console.log(`PaymentIntent ${paymentIntent.id} succeeded for ${paymentIntent.amount} cents.`);
// Aquí actualizas tu base de datos
// Ejemplo: marcar orden como pagada
await updateOrderStatus(paymentIntent.metadata.orderId, 'paid');
}
res.json({received: true});
});
app.listen(3000, () => console.log('Webhook server running on port 3000'));Ahora, mejoremos el código para manejar errores y logging de forma más robusta:
app.post('/webhook', async (req, res) => {
const sig = req.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(
req.rawBody || req.body,
sig,
process.env.STRIPE_WEBHOOK_SECRET
);
} catch (err) {
console.error('Webhook signature verification failed:', err.message);
// Registrar el error en un servicio como Sentry
captureError(err);
return res.status(400).send(`Webhook Error: ${err.message}`);
}
try {
switch (event.type) {
case 'payment_intent.succeeded':
await handlePaymentIntentSucceeded(event.data.object);
break;
default:
console.log(`Unhandled event type: ${event.type}`);
}
res.json({received: true});
} catch (error) {
console.error('Error processing webhook:', error);
res.status(500).send('Internal Server Error');
}
});
async function handlePaymentIntentSucceeded(paymentIntent) {
console.log(`Processing payment ${paymentIntent.id} for order ${paymentIntent.metadata.orderId}`);
// Lógica de negocio: actualizar base de datos, enviar email, etc.
await updateOrder(paymentIntent.metadata.orderId, {
status: 'paid',
paymentId: paymentIntent.id,
amount: paymentIntent.amount / 100 // Convertir de centavos a unidades
});
await sendReceiptEmail(paymentIntent.receipt_email, paymentIntent.amount);
}Errores comunes
- No verificar la firma del webhook: Esto expone tu sistema a ataques donde alguien podría enviar eventos falsos. Siempre usa stripe.webhooks.constructEvent con tu secreto de webhook.
- No manejar eventos duplicados: Stripe puede reenviar webhooks si no recibe una respuesta HTTP 200. Implementa idempotencia guardando los IDs de evento procesados para evitar acciones repetidas.
- Procesar eventos de forma síncrona: Si tu lógica de negocio tarda mucho (como enviar emails), el webhook podría timeout. Usa colas de mensajes (como RabbitMQ o AWS SQS) para procesar de forma asíncrona.
- No probar webhooks en entorno local: Usa la CLI de Stripe para reenviar eventos a tu localhost durante el desarrollo. Ejecuta
stripe listen --forward-to localhost:3000/webhook. - Olvidar el raw body: En algunos frameworks, necesitas acceder al cuerpo crudo de la solicitud para la verificación. En Express, usa middleware como
body-parserconverifypara preservar el raw body.
Checklist de dominio
- Configurar un endpoint HTTPS para recibir webhooks en tu servidor.
- Verificar la firma de cada webhook usando el secreto de Stripe.
- Manejar el evento payment_intent.succeeded actualizando el estado del pedido en tu base de datos.
- Implementar idempotencia para evitar procesar eventos duplicados.
- Probar webhooks en desarrollo con la CLI de Stripe o el dashboard.
- Manejar errores graciosamente y registrar logs para depuración.
- Usar procesamiento asíncrono para tareas largas como envío de emails.
Implementar un webhook para payment_intent.succeeded en un proyecto existente
En este ejercicio, integrarás el manejo de webhooks en una aplicación backend simulada. Sigue estos pasos:
- Configura el entorno: Crea un nuevo directorio para el proyecto y ejecuta
npm init -y. Instala las dependencias necesarias:npm install express stripe body-parser. - Crea un servidor básico: En un archivo
server.js, configura un servidor Express que escuche en el puerto 3000. Asegúrate de usarbody-parserpara manejar JSON y preservar el raw body para la verificación de webhooks. - Implementa el endpoint de webhook: Agrega una ruta POST en
/webhookque:- Verifique la firma del webhook usando
stripe.webhooks.constructEvent. - Maneje el evento payment_intent.succeeded extrayendo el ID del pago y la cantidad.
- Simule una actualización de base de datos imprimiendo un mensaje en consola.
- Responda con HTTP 200 si todo sale bien, o con un error apropiado en caso de fallo.
- Verifique la firma del webhook usando
- Prueba con la CLI de Stripe: Instala la CLI de Stripe y ejecuta
stripe listen --forward-to localhost:3000/webhook. En otra terminal, dispara un evento de prueba constripe trigger payment_intent.succeededy verifica que tu servidor lo procese correctamente. - Agrega idempotencia: Modifica el código para almacenar los IDs de eventos procesados en un array en memoria (o en una base de datos simple) y evita procesar eventos duplicados.
- Usa
body-parsercon la opciónverifypara preservar el raw body necesario para la verificación de la firma. - Recuerda que el secreto del webhook es diferente a tu clave secreta de Stripe; lo encuentras en el dashboard de Stripe en la sección de webhooks.
- Para simular la base de datos, puedes usar un objeto JavaScript simple como
let processedEvents = {}para almacenar los IDs.
Evalua tu comprension
Completa el quiz interactivo de arriba para ganar XP.