Beta privadaMailbeam está en beta privada: la API pública todavía no está abierta.Únete a la lista de espera

Webhooks

Mailbeam puede enviar un evento por POST a tu servidor en lugar de obligarte a consultarlo tú.

Configurar un endpoint

  1. Ve a Webhooks en tu panel
  2. Escribe una URL HTTPS y elige los eventos que quieras
  3. Copia el secreto de firma: se muestra una sola vez
  4. Pulsa Send test para comprobar que tu handler responde

Los endpoints tienen que ser URLs HTTPS accesibles públicamente. Rechazamos HTTP a secas, y rechazamos las direcciones privadas y de enlace local, porque la petición la hacen nuestros servidores. Para desarrollar en local, expón tu máquina con ngrok o Cloudflare Tunnel.

Eventos disponibles

EventoCuándo se dispara
batch.completedUn trabajo por lotes termina de procesarse
batch.failedUn trabajo por lotes se detiene con un error
quota.thresholdLlegas al 80 % o al 100 % de tu cuota mensual

Esa es toda la lista. Es corta porque solo enviamos eventos que podemos observar directamente. Dos eventos que esta documentación anunciaba antes —email.bounced y email.mx_degraded— se han eliminado: un rebote ocurre en tu flujo de correo, no en el nuestro, y no vigilamos los registros MX de un dominio a lo largo del tiempo. Si alguno de los dos te resultaría útil, dínoslo y miramos qué haría falta.

Estructura del payload

Todos los payloads de webhook siguen el mismo sobre:

{
  "id": "evt_9f2c1d7a-4b83-4a1e-9f0e-2b6c5d8a1f34",
  "type": "batch.completed",
  "created_at": "2026-08-10T14:32:00Z",
  "data": {
    "job_id": "job_9f2c1d7a-4b83-4a1e-9f0e-2b6c5d8a1f34",
    "total": 5000,
    "processed": 5000,
    "valid": 4213,
    "invalid": 787,
    "download_url": "https://api.mailbeam.dev/v1/jobs/job_9f2c1d7a-4b83-4a1e-9f0e-2b6c5d8a1f34/results",
    "results_expire_at": "2026-08-13T14:32:00Z"
  }
}

La entrega es al menos una vez. Preferimos enviar un duplicado antes que perder un evento, así que si una respuesta se pierde de camino a nosotros verás el mismo evento dos veces. Deduplica por id, que es estable en todos los reintentos y en todos los endpoints a los que se envió el evento.

Verificación de la firma HMAC

Toda petición lleva una cabecera X-Mailbeam-Signature. Verifícala antes de fiarte del payload: la URL es lo único más que protege tu handler, y las URL se filtran.

La firma es un resumen HMAC-SHA256 del cuerpo en crudo de la petición, con la clave del secreto de firma de tu endpoint.

import crypto from "crypto";

export function verifyWebhookSignature(
  body: string | Buffer,
  signature: string,
  secret: string
): boolean {
  const expected = crypto
    .createHmac("sha256", secret)
    .update(body)
    .digest("hex");
  // Usa timingSafeEqual para evitar ataques de temporización
  return crypto.timingSafeEqual(
    Buffer.from(`sha256=${expected}`, "utf8"),
    Buffer.from(signature, "utf8")
  );
}

// En tu handler de Express:
app.post("/webhooks/mailbeam", express.raw({ type: "application/json" }), (req, res) => {
  const sig = req.headers["x-mailbeam-signature"] as string;

  if (!verifyWebhookSignature(req.body, sig, process.env.MAILBEAM_WEBHOOK_SECRET!)) {
    return res.status(400).send("Invalid signature");
  }

  const event = JSON.parse(req.body.toString());
  // Trata el evento...
  res.json({ received: true });
});

Usa express.raw(), no express.json(). Necesitas los bytes del cuerpo en crudo para verificar la firma. Si analizas el JSON antes, la firma no cuadrará.

Política de reintentos

Cualquier respuesta fuera del rango 2xx, o la ausencia de respuesta en 30 segundos, cuenta como intento fallido. No seguimos redirecciones: un 3xx también es un fallo.

IntentoEspera
1.er reintento5 minutos
2.º reintento30 minutos
3.er reintento2 horas
4.º reintento8 horas
5.º reintento24 horas

Los reintentos los lanza un planificador que se ejecuta cada cinco minutos, así que la espera de la tabla es un mínimo, no un tiempo exacto.

Cuando han fallado el intento inicial y los cinco reintentos —unas 34 horas— el endpoint queda desactivado y avisamos por email a la persona propietaria de la cuenta. No se encola nada más para él hasta que lo reactives desde el panel. Arregla tu receptor, reactívalo y envía un evento de prueba antes de volver a depender de él.

Un evento de prueba enviado desde el panel es un intento único. Nunca entra en la escalera de reintentos y nunca desactiva un endpoint.

Cómo responder a los webhooks

Devuelve un estado 2xx lo antes posible. Descarga el procesamiento pesado en una cola:

app.post("/webhooks/mailbeam", async (req, res) => {
  // Verifica la firma primero
  if (!verifyWebhookSignature(req.body, req.headers["x-mailbeam-signature"], secret)) {
    return res.status(400).send("Invalid signature");
  }

  // Confirma de inmediato
  res.json({ received: true });

  // Procesa de forma asíncrona
  await queue.add("process-mailbeam-event", JSON.parse(req.body.toString()));
});

Tu secreto de firma

El secreto se genera al crear el endpoint y se muestra una sola vez. Lo guardamos cifrado y no con un hash, porque a diferencia de una clave de API tenemos que usarlo: cada entrega se firma con él. Si lo pierdes, borra el endpoint y crea uno nuevo.