Handshake de verificación del endpoint
Antes de guardar un endpoint, Omnero Mail envía webhook.endpoint_verification. Parsea el JSON y devuelve { "challenge": payload.data.challenge } con HTTP 2xx antes de cinco segundos. Redirecciones, timeout, respuestas no 2xx, JSON inválido o un challenge diferente impiden la creación. El handshake demuestra que controlas un handler funcional; su firma aún no debe considerarse confiable porque el secreto se revela solo después de crear correctamente.
const payload = JSON.parse(rawBody);
if (payload.type === 'webhook.endpoint_verification') {
return Response.json({ challenge: payload.data.challenge });
}
Verifica HMAC-SHA256 sobre los bytes crudos
Para cada evento operativo, lee los tres headers MailDeck-Webhook antes de parsear. Construye id + "." + timestamp + "." + rawBody, calcula HMAC-SHA256 con tu secreto whsec_ y compara v1=<hex> en tiempo constante. Rechaza headers ausentes, timestamps alejados más de cinco minutos de tu reloj, hex malformado o una firma diferente. Nunca reconstruyas el body con JSON.stringify: incluso un JSON equivalente produce bytes distintos.
import { createHmac, timingSafeEqual } from 'node:crypto';
const id = request.headers.get('maildeck-webhook-id')!;
const timestamp = request.headers.get('maildeck-webhook-timestamp')!;
const supplied = request.headers.get('maildeck-webhook-signature')!;
const age = Math.abs(Date.now() / 1000 - Number(timestamp));
if (!Number.isFinite(age) || age > 300) throw new Error('stale webhook');
const expected = createHmac('sha256', process.env.MAILDECK_WEBHOOK_SECRET!)
.update(`${id}.${timestamp}.${rawBody}`)
.digest('hex');
const received = supplied.startsWith('v1=') ? supplied.slice(3) : '';
const valid = /^[a-f0-9]{64}$/.test(received) && timingSafeEqual(
Buffer.from(received, 'hex'), Buffer.from(expected, 'hex')
);
if (!valid) throw new Error('invalid webhook signature');
Envelope y headers de entrega
Las solicitudes operativas usan Content-Type: application/json y User-Agent: MailDeck-Webhooks/1.0. El envelope JSON contiene id, type, apiVersion, occurredAt y data. data siempre incluye tipo de objeto e ID canónicos más los campos propios del evento; el consumidor debe ignorar campos y tipos desconocidos para conservar compatibilidad futura.
- MailDeck-Webhook-Id — event ID estable y clave de deduplicación
- MailDeck-Webhook-Timestamp — segundos Unix para impedir replay
- MailDeck-Webhook-Signature — v1=<HMAC de 64 caracteres hex>
Cifrado en reposo frente a firma en tránsito
Omnero Mail genera un valor whsec_ aleatorio y lo revela una sola vez. Internamente se almacena como un envelope AES-256-GCM versionado, con IV nuevo de 96 bits y authentication tag. El dispatcher lo descifra solo en memoria del worker, firma los bytes exactos que envía y descarta el texto plano con la invocación. El body no usa cifrado de aplicación: HTTPS protege el transporte y HMAC demuestra autenticidad e integridad. El receptor verifica la firma; nunca descifra el JSON.
Catálogo de eventos
Suscríbete a tipos individuales o a * para todos los soportados. El ciclo de mensaje incluye message.received, message.queued, message.sent, message.delivered, message.bounced, message.failed y message.updated. Los recursos emiten mailbox.created, mailbox.updated, mailbox.suspended y domain.health_changed.
Diseña para entrega al menos una vez
Parsea y procesa el evento únicamente después de verificar la firma. En una transacción durable, guarda el event ID bajo una restricción única y encola o aplica el efecto. Una repetición conserva el mismo ID y puede confirmarse sin duplicar trabajo. Responde 2xx después de aceptar durablemente, no solo después de leer el request.
- Sin garantía de orden global
- Cinco segundos para responder
- Reintentos automáticos y retención en dead-letter queue
- Replay manual desde la actividad de integraciones
Reintentos, fallos terminales y salud del endpoint
Cualquier timeout, resolución insegura de URL, redirect, fallo de red o respuesta no 2xx cuenta como intento fallido. Omnero Mail registra el fallo y reintenta mediante la cola estándar hasta cinco intentos. Las entregas agotadas quedan dead y disponibles para replay manual. Diez fallos consecutivos desactivan el endpoint; una entrega correcta reinicia el contador.
Recupérate después de una caída
Usa GET /v1/events?limit=25&cursor=<timestamp ISO> con messages:read para conciliar el historial canónico. Procesa items, guarda nextCursor solo después de aceptar durablemente y continúa hasta null. Los webhooks optimizan latencia; events recupera completitud sin asumir orden global.
Prueba el pipeline con eventos sandbox
Desde Dashboard → Integraciones, elige una aplicación sandbox y un tipo de evento. Omnero Mail crea un evento canónico sintético marcado demo: true, lo envía únicamente a los endpoints suscritos de esa aplicación y aplica la misma firma HMAC, reintentos, actividad y replay que en una entrega operativa.