Webhooks y Eventos

Cuando algo pasa con un email que enviaste (se entregó, se abrió, rebotó), Resend puede notificarte en tiempo real enviando un POST request a una URL que tú configuras. Eso es un webhook: una llamada HTTP automática que tu servidor recibe cada vez que ocurre un evento.

Eventos Disponibles

Resend puede notificarte sobre los siguientes eventos:

EventoDescripción
email.sentEl email fue aceptado por Resend y está en cola para envío
email.deliveredEl email fue entregado exitosamente al servidor del destinatario
email.delivery_delayedLa entrega del email se retrasó (el servidor destino tiene problemas temporales)
email.bouncedEl email rebotó -- la dirección no existe, el buzón está lleno, u otro error permanente
email.complainedEl destinatario marcó tu email como spam
email.openedEl destinatario abrió el email (detectado vía tracking pixel)
email.clickedEl destinatario hizo clic en un link dentro del email

Configurar un Webhook en el Dashboard

  1. En el dashboard de Resend, ve a Webhooks en el menú lateral
  2. Haz clic en Add Endpoint
  3. Ingresa la URL de tu endpoint (por ejemplo https://tuapp.com/api/webhooks/resend)
  4. Selecciona los eventos a los que quieres suscribirte
  5. Guarda la configuración y copia el signing secret -- lo necesitas para verificar las firmas

Crear el Endpoint en NextJS

Este es un Route Handler básico que recibe y procesa los eventos de Resend:

typescript
// app/api/webhooks/resend/route.ts
import { NextResponse } from 'next/server'
 
export async function POST(request: Request) {
  const payload = await request.json()
 
  switch (payload.type) {
    case 'email.delivered':
      console.log('Email entregado:', payload.data.email_id)
      break
    case 'email.bounced':
      console.log('Email rebotó:', payload.data.email_id)
      // Marcar email como inválido en tu base de datos
      break
    case 'email.complained':
      console.log('Marcado como spam:', payload.data.email_id)
      // Desuscribir automáticamente
      break
  }
 
  return NextResponse.json({ received: true })
}

Verificar la Firma del Webhook

El endpoint anterior funciona, pero tiene un problema de seguridad: cualquiera que conozca la URL puede enviar requests falsos. Para evitarlo, verifica la firma que Resend incluye en cada webhook.

Resend usa Svix para manejar webhooks. Svix firma cada request con un secret que solo tú y Resend conocen. Para verificar la firma, instala el paquete de Svix:

bash
npm install svix

Luego actualiza tu endpoint para verificar antes de procesar:

typescript
// app/api/webhooks/resend/route.ts
import { Webhook } from 'svix'
 
const wh = new Webhook(process.env.RESEND_WEBHOOK_SECRET!)
 
export async function POST(request: Request) {
  const body = await request.text()
  const headers = {
    'svix-id': request.headers.get('svix-id')!,
    'svix-timestamp': request.headers.get('svix-timestamp')!,
    'svix-signature': request.headers.get('svix-signature')!,
  }
 
  try {
    const payload = wh.verify(body, headers)
    // Procesar el evento verificado
    return new Response('OK', { status: 200 })
  } catch {
    return new Response('Firma inválida', { status: 401 })
  }
}
Verifica siempre la firma en producción

Sin verificación de firma, cualquiera puede enviar requests falsos a tu endpoint y disparar acciones no autorizadas en tu aplicación. En desarrollo local puedes omitirlo para pruebas rápidas, pero en producción es obligatorio.

Reintentos automáticos

Resend usa Svix para webhooks, que garantiza at-least-once delivery (entrega al menos una vez) con reintentos automáticos. Si tu endpoint devuelve un error (status 5xx), Svix reintentará el envío con backoff exponencial durante varias horas. Esto significa que tu endpoint puede recibir el mismo evento más de una vez -- diseña tu lógica para que sea idempotente.

Testing en Local

Tu endpoint en localhost no es accesible desde internet, así que Resend no puede enviar webhooks a tu máquina directamente. Tienes dos opciones para probar en desarrollo:

Opción 1: ngrok

Expone tu localhost con una URL pública temporal:

bash
ngrok http 3000

Copia la URL que te da ngrok (algo como https://abc123.ngrok.io) y configúrala como endpoint del webhook en el dashboard de Resend.

Opción 2: Svix CLI

Svix ofrece un CLI que te permite recibir webhooks directamente en tu máquina:

bash
svix listen http://localhost:3000/api/webhooks/resend

Ambas opciones funcionan bien para desarrollo. Elige la que te resulte más cómoda.