Descripción general
Los webhooks permiten que tu aplicación reciba notificaciones en tiempo real cuando ocurren eventos de correo. Hermes envía una solicitud HTTP POST a tu endpoint por cada evento.
Configurar un webhook
- En el dashboard de Hermes, ve a Webhooks → Agregar Webhook
- Ingresa la URL de tu endpoint (debe ser HTTPS)
- Selecciona los eventos que deseas recibir
- Haz clic en Guardar
Tipos de eventos
| Evento | Descripción |
|---|---|
delivered | El correo fue entregado exitosamente al servidor del destinatario |
bounce_hard | Falla de entrega permanente — la dirección no existe o el dominio la rechazó |
bounce_soft | Falla de entrega temporal — buzón lleno o servidor temporalmente no disponible |
complaint | El destinatario marcó el correo como spam |
Nota: Los eventos de apertura y clic de correo no están disponibles aún.
Estructura del payload
Todos los eventos comparten el mismo sobre:
{
"event": "delivered",
"email_id": "a1b2c3d4-...",
"timestamp": "2026-05-29T14:32:00.000",
"metadata": {}
}El campo metadata contiene detalles específicos del evento.
Payload de bounce_hard y bounce_soft
{
"event": "bounce_hard",
"email_id": "a1b2c3d4-...",
"timestamp": "2026-05-29T14:32:10.000",
"metadata": {
"bounce_type": "Permanent",
"bounce_subtype": "General",
"bounced_recipients": ["[email protected]"],
"diagnostic_codes": ["smtp; 550 5.1.1 The email account that you tried to reach does not exist"]
}
}Los valores de bounce_type de SES: Permanent, Transient, Undetermined.
Payload de complaint
{
"event": "complaint",
"email_id": "a1b2c3d4-...",
"timestamp": "2026-05-29T14:32:15.000",
"metadata": {
"complaint_feedback_type": "abuse",
"complained_recipients": ["[email protected]"]
}
}Verificar firmas de webhook
Cada solicitud de Hermes incluye un encabezado X-Hermes-Signature. Siempre verifica esto antes de procesar.
La firma es sha256= seguido del digest hexadecimal HMAC-SHA256 del cuerpo bruto de la solicitud, usando tu clave de firma de webhook. El cuerpo es JSON compacto (sin espacios adicionales).
Python
import hmac
import hashlib
def verify_signature(payload: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode(),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(f"sha256={expected}", signature)
@app.post("/webhooks/hermes")
async def handle_webhook(request: Request):
payload = await request.body()
sig = request.headers.get("X-Hermes-Signature", "")
if not verify_signature(payload, sig, WEBHOOK_SECRET):
raise HTTPException(status_code=401)
event = await request.json()
# procesar evento...Node.js
const crypto = require('crypto');
function verifySignature(payload, signature, secret) {
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature)
);
}
app.post('/webhooks/hermes', express.raw({ type: 'application/json' }), (req, res) => {
if (!verifySignature(req.body, req.headers['x-hermes-signature'] || '', process.env.WEBHOOK_SECRET)) {
return res.status(401).json({ error: 'Firma inválida' });
}
const event = JSON.parse(req.body);
res.json({ received: true });
});Comportamiento de reintentos
Si tu endpoint devuelve un estado no-2xx o tarda más de 10 segundos, Hermes reintenta con backoff exponencial:
| Intento | Espera |
|---|---|
| 1er reintento | 1 minuto |
| 2do reintento | 2 minutos |
| 3er reintento | 4 minutos |
Después de 3 reintentos fallidos, el evento se descarta.
Buenas prácticas
- Siempre verifica el encabezado
X-Hermes-Signatureantes de procesar - Responde en menos de 10 segundos con un estado 2xx — procesa de forma asíncrona si es necesario
- Usa
email_idpara idempotencia — puedes recibir el mismo evento más de una vez - Usa únicamente endpoints HTTPS
