Ir al contenido

Webhooks

Los webhooks te permiten recibir notificaciones en tiempo real cuando ocurren eventos en tu cuenta: envíos entregados, fallos, desuscripciones, y eventos de campañas y sequences.

Cada API key puede tener configurada una webhook_url independiente. Cuando se dispara un evento, MailerDash hace un POST a esa URL con el payload del evento firmado con HMAC-SHA256.


El webhook se configura por API key usando PATCH /v1/client/keys/:id con los campos webhook_url (y opcionalmente una webhook_secret que puedes rotar después). Si al configurar la URL no existe ya un secreto, el sistema genera uno automáticamente.

Ventana de terminal
curl -X PATCH https://api.mailerdash.com/v1/client/keys/mi-key \
-H "Authorization: Bearer $MAILERDASH_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "webhook_url": "https://mi-app.com/webhooks/mailerdash" }'

Para ver la configuración actual de un webhook (sin exponer el secret):

Ventana de terminal
GET /v1/client/keys/:id/webhook

Respuesta:

{
"webhook_url": "https://mi-app.com/webhooks/mailerdash",
"secret_set": true,
"secret_masked": "whsec_••••••••••••3a9f"
}

Para desactivar el webhook (URL + secret quedan en null):

Ventana de terminal
DELETE /v1/client/keys/:id/webhook

Cada evento llega como un POST con Content-Type: application/json. El cuerpo tiene siempre los campos event, ts y app, más campos específicos según el tipo de evento.

{
"event": "email.sent",
"ts": "2026-06-22T15:30:00.000Z",
"app": "mi-app",
"request_id": "req_abc123",
"to": ["usuario@ejemplo.com"],
"subject": "Tu factura de junio",
"message_id": "<abc123@mailerdash.com>"
}
EventoDescripción
email.sentCorreo transaccional enviado exitosamente
email.failedCorreo transaccional falló al enviarse
contact.unsubscribedUn contacto se desuscribió
campaign.email.sentEmail de campaña enviado a un contacto
campaign.email.failedEmail de campaña falló para un contacto
campaign.completedCampaña completó el envío a todos sus destinatarios
sequence.subscribedContacto suscrito a una sequence
sequence.step.sentPaso de sequence enviado
sequence.step.failedPaso de sequence falló
sequence.completedSequence completada para un contacto
sequence.cancelledSequence cancelada (campo reason con motivo)
  • email.sent / email.failed: request_id, to (array), subject, message_id. En failed incluye error.
  • contact.unsubscribed: email.
  • campaign.email.sent / campaign.email.failed: email, campaign_id. En failed incluye error.
  • campaign.completed: campaign_id, name, sent_count, failed_count, delivery_rate.
  • sequence.*: email, campaign_id (id de la sequence). En sequence.cancelled incluye reason (unsubscribed, manual, bounced, complained, suppressed).

Cada request incluye dos headers de seguridad:

  • X-Md-Signature: sha256=<hmac-hex>
  • X-Md-Timestamp: timestamp ISO en el momento del envío

La firma se calcula sobre el string <timestamp>.<cuerpo_crudo> usando tu webhook_secret.

const crypto = require('crypto');
function verifyWebhook(req, webhookSecret) {
const signature = req.headers['x-md-signature']; // "sha256=abc123..."
const timestamp = req.headers['x-md-timestamp']; // "2026-06-22T15:30:00.000Z"
const rawBody = req.body; // Buffer o string — debe ser el cuerpo crudo sin parsear
if (!signature || !timestamp) return false;
// Verificar que el timestamp no sea demasiado viejo (anti-replay)
const diff = Math.abs(Date.now() - new Date(timestamp).getTime());
if (diff > 5 * 60 * 1000) return false; // más de 5 minutos → rechazar
// Calcular HMAC sobre "<timestamp>.<cuerpo_crudo>"
const signedPayload = `${timestamp}.${rawBody}`;
const expected = 'sha256=' + crypto
.createHmac('sha256', webhookSecret)
.update(signedPayload)
.digest('hex');
// Comparar con tiempo constante para evitar timing attacks
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
// Ejemplo con Express — parsea el body como Buffer para preservar el raw body
app.post('/webhooks/mailerdash', express.raw({ type: 'application/json' }), (req, res) => {
const valid = verifyWebhook(req, process.env.MAILERDASH_WEBHOOK_SECRET);
if (!valid) return res.status(401).send('Invalid signature');
const event = JSON.parse(req.body);
console.log('Evento recibido:', event.event);
res.sendStatus(200);
});

Si tu endpoint no responde con un 2xx, MailerDash reintenta automáticamente:

  1. Burst inicial (in-process, máx. 3 intentos): después del primer fallo, reintenta a los 1s, 5s y 25s.
  2. Queue con backoff progresivo (si el burst falla por completo): 5 min → 30 min → 2 h → 6 h → 24 h (×3).
  3. Si agotan todos los intentos, el delivery queda en estado failed (DLQ) visible desde el historial.

El timeout por intento es de 10 segundos. Tu endpoint debe responder dentro de ese tiempo.


Genera un nuevo webhook_secret e invalida el anterior. Usa esto si crees que tu secret fue comprometido.

Ventana de terminal
POST /v1/client/keys/:id/webhook/rotate-secret

Dispara un evento webhook.test al URL configurado para verificar que tu endpoint lo recibe correctamente.

Ventana de terminal
POST /v1/client/keys/:id/webhook/test

Lista las últimas entregas con su estado, código HTTP y timestamp.

Ventana de terminal
GET /v1/client/keys/:id/webhook/deliveries

Parámetros de query: limit (default 50, máx. 200), offset.

Incluye el payload completo y el body de respuesta de tu endpoint (hasta 2 KB).

Ventana de terminal
GET /v1/client/keys/:id/webhook/deliveries/:dId

Re-dispara un evento específico con el URL y secret actuales de la key.

Ventana de terminal
POST /v1/client/keys/:id/webhook/deliveries/:dId/retry

  • En producción, MailerDash solo entrega a URLs HTTPS.
  • Las URLs que resuelven a IPs privadas o reservadas (loopback, RFC1918, metadata de cloud) son bloqueadas para prevenir SSRF.
  • El webhook_secret solo se muestra enmascarado en las respuestas de API; nunca se expone completo después de la creación.

Ver referencia completa en /reference/platform/.