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.
Configurar tu webhook
Sección titulada «Configurar tu webhook»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.
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):
GET /v1/client/keys/:id/webhookRespuesta:
{ "webhook_url": "https://mi-app.com/webhooks/mailerdash", "secret_set": true, "secret_masked": "whsec_••••••••••••3a9f"}Para desactivar el webhook (URL + secret quedan en null):
DELETE /v1/client/keys/:id/webhookPayload EventoWebhook
Sección titulada «Payload EventoWebhook»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>"}Eventos disponibles
Sección titulada «Eventos disponibles»| Evento | Descripción |
|---|---|
email.sent | Correo transaccional enviado exitosamente |
email.failed | Correo transaccional falló al enviarse |
contact.unsubscribed | Un contacto se desuscribió |
campaign.email.sent | Email de campaña enviado a un contacto |
campaign.email.failed | Email de campaña falló para un contacto |
campaign.completed | Campaña completó el envío a todos sus destinatarios |
sequence.subscribed | Contacto suscrito a una sequence |
sequence.step.sent | Paso de sequence enviado |
sequence.step.failed | Paso de sequence falló |
sequence.completed | Sequence completada para un contacto |
sequence.cancelled | Sequence cancelada (campo reason con motivo) |
Campos por evento
Sección titulada «Campos por evento»email.sent/email.failed:request_id,to(array),subject,message_id. Enfailedincluyeerror.contact.unsubscribed:email.campaign.email.sent/campaign.email.failed:email,campaign_id. Enfailedincluyeerror.campaign.completed:campaign_id,name,sent_count,failed_count,delivery_rate.sequence.*:email,campaign_id(id de la sequence). Ensequence.cancelledincluyereason(unsubscribed,manual,bounced,complained,suppressed).
Verificar la firma HMAC-SHA256
Sección titulada «Verificar la firma HMAC-SHA256»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 bodyapp.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);});import hmacimport hashlibimport timefrom datetime import datetime, timezone
def verify_webhook(signature: str, timestamp: str, raw_body: bytes, secret: str) -> bool: # Verificar que el timestamp no sea demasiado viejo (anti-replay) ts = datetime.fromisoformat(timestamp.replace('Z', '+00:00')) diff = abs((datetime.now(timezone.utc) - ts).total_seconds()) if diff > 300: # más de 5 minutos → rechazar return False
signed_payload = f"{timestamp}.{raw_body.decode('utf-8')}".encode('utf-8') expected = 'sha256=' + hmac.new( secret.encode('utf-8'), signed_payload, hashlib.sha256 ).hexdigest()
return hmac.compare_digest(signature, expected)Reintentos con backoff
Sección titulada «Reintentos con backoff»Si tu endpoint no responde con un 2xx, MailerDash reintenta automáticamente:
- Burst inicial (in-process, máx. 3 intentos): después del primer fallo, reintenta a los 1s, 5s y 25s.
- Queue con backoff progresivo (si el burst falla por completo): 5 min → 30 min → 2 h → 6 h → 24 h (×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.
Endpoints de gestión
Sección titulada «Endpoints de gestión»Rotar el secret
Sección titulada «Rotar el secret»Genera un nuevo webhook_secret e invalida el anterior. Usa esto si crees que tu secret fue comprometido.
POST /v1/client/keys/:id/webhook/rotate-secretEnviar un evento de prueba
Sección titulada «Enviar un evento de prueba»Dispara un evento webhook.test al URL configurado para verificar que tu endpoint lo recibe correctamente.
POST /v1/client/keys/:id/webhook/testHistorial de entregas
Sección titulada «Historial de entregas»Lista las últimas entregas con su estado, código HTTP y timestamp.
GET /v1/client/keys/:id/webhook/deliveriesParámetros de query: limit (default 50, máx. 200), offset.
Detalle de una entrega
Sección titulada «Detalle de una entrega»Incluye el payload completo y el body de respuesta de tu endpoint (hasta 2 KB).
GET /v1/client/keys/:id/webhook/deliveries/:dIdReintentar una entrega manualmente
Sección titulada «Reintentar una entrega manualmente»Re-dispara un evento específico con el URL y secret actuales de la key.
POST /v1/client/keys/:id/webhook/deliveries/:dId/retrySeguridad adicional
Sección titulada «Seguridad adicional»- 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_secretsolo se muestra enmascarado en las respuestas de API; nunca se expone completo después de la creación.
Ver referencia completa en /reference/platform/.