Eventos en tiempo real
Los webhooks permiten que servicios te notifiquen cuando algo pasa.
Cómo funcionan
Evento en servicio externo
↓
POST a tu endpoint
↓
Tu código procesa
Ejemplo: Stripe webhook
// app/api/webhooks/stripe/route.ts
export async function POST(request: Request) {
const body = await request.text()
const signature = request.headers.get('stripe-signature')!
const event = stripe.webhooks.constructEvent(
body,
signature,
process.env.STRIPE_WEBHOOK_SECRET!
)
switch (event.type) {
case 'payment_intent.succeeded':
// Marcar orden como pagada
break
case 'customer.subscription.deleted':
// Cancelar suscripción
break
}
return new Response('OK')
}
Verificar firma
Siempre verifica que el webhook viene del servicio real:
import crypto from 'crypto'
function verifySignature(payload: string, signature: string, secret: string) {
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex')
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
)
}
Servicios con webhooks
- Stripe (pagos)
- GitHub (commits, PRs)
- Slack (mensajes)
- Twilio (SMS, llamadas)
🔐 Webhooks de Pago: Seguridad Crítica
Los webhooks de pago son el punto más vulnerable de tu aplicación. Un atacante puede simular un pago exitoso.
Idempotencia: No proceses dos veces
Stripe puede reenviar el mismo webhook múltiples veces. Sin idempotencia, cobras/acreditas doble.
export async function POST(request: Request) {
const event = await verifyAndParseWebhook(request)
// ⚠️ CRÍTICO: Verificar si ya procesamos este evento
const processed = await db.query(
'SELECT 1 FROM processed_webhooks WHERE event_id = $1',
[event.id]
)
if (processed.rows.length > 0) {
// Ya procesado, responder OK sin hacer nada
return new Response('Already processed', { status: 200 })
}
// Procesar el evento
await processPaymentEvent(event)
// Marcar como procesado DESPUÉS de procesar exitosamente
await db.query(
'INSERT INTO processed_webhooks (event_id, processed_at) VALUES ($1, NOW())',
[event.id]
)
return new Response('OK')
}
Responde rápido, procesa después
Stripe espera respuesta en 20 segundos. Si tu procesamiento es lento, hazlo async:
export async function POST(request: Request) {
const event = await verifyAndParseWebhook(request)
// Responder inmediatamente
// Procesar en background (cola, worker, etc.)
await queue.add('process-payment', { eventId: event.id })
return new Response('OK') // < 1 segundo
}
Errores comunes en Fintech
| Error | Consecuencia | Solución |
|---|---|---|
| No validar firma | Fraude: pagos falsos | constructEvent() siempre |
| No manejar duplicados | Cobro/acreditación doble | Tabla de idempotencia |
| Timeout en procesamiento | Stripe reintenta = duplicados | Responder rápido, procesar async |
| No loguear eventos | Imposible debuggear | Log completo con timestamp |