Webhooks

Webhooks de ReallyQuickEmails — eventos email.send/delivery/bounce/complaint/reject/deliverydelay/open/click/inbound, headers X-RQE-Signature y X-RQE-Environment, verificación HMAC-SHA256, routing webhook_url/webhook_url_dev/webhook_environments, payload de replies con adjuntos, retries.

ReallyQuickEmails te dispara un webhook por cada evento que ocurre durante la vida de un email: aceptación, entrega, rebote, queja, apertura, click y respuesta del destinatario.

Cómo recibir webhooks

Configura la URL en el dashboard

En el dashboard, entra a tu proyecto y abre Configuración → Integraciones → Webhooks. Pega la URL pública de tu endpoint. Las URLs disponibles por proyecto y qué recibe cada una están en Routing live / test / environment.

Expón tu endpoint local

Para desarrollo, crea un túnel hacia tu servidor local con una herramienta como ngrok (ngrok http 3000) y usa la URL pública generada como URL del webhook.

Valida la firma

Cada POST incluye el header X-RQE-Signature. Verifica el HMAC sobre el body raw antes de procesar el evento. Ver más en Verificación HMAC.

Responde 200

Devuelve un status 2xx lo antes posible; cualquier otro status, timeout o error de red activa reintentos. Ver más en Retry y respuesta esperada.

Pasa a producción

Reemplaza la URL del túnel por la URL pública de tu servidor. El flag is_test del payload te permite distinguir el tráfico de envíos con sk_test_*.

Eventos

eventCuándo disparaDatos extra en data
email.sendRQE aceptó y despachó el envío
email.deliveryEl servidor del destinatario aceptó el email
email.bounceEmail rebotadobounce_type, bounce_subtype
email.complaintDestinatario marcó como spamcomplaint_feedback_type
email.rejectEnvío rechazado antes de salir
email.deliverydelayEntrega temporalmente demorada
email.openDestinatario abrió el email (pixel cargado)
email.clickClick en un linkurl_clicked
email.inboundDestinatario respondió tu email(ver sección Replies)

Payload outbound

Todos los eventos outbound (email.send, email.delivery, email.bounce, email.complaint, email.reject, email.deliverydelay, email.open, email.click) comparten la misma estructura top-level. Lo que cambia es data.

json
{
  "event": "email.delivery",
  "timestamp": "2026-04-29T15:30:42.123Z",
  "project_id": "uuid",
  "is_test": false,
  "environment": "staging",
  "data": { "...": "..." }
}
CampoTipoDescripción
eventstringTipo del evento (ver tabla arriba).
timestampstring ISOMomento en que RQE despachó el webhook.
project_iduuidTu proyecto.
is_testbooleantrue si el email se envió con sk_test_*.
environmentstring(Opcional) presente solo si el envío incluyó environment. Ver Webhook environments.
dataobjectDetalle del evento (ver siguiente tabla).

Campos de data

CampoPresente enDescripción
activity_idtodosUUID del email original (lo recibes en la respuesta de /v1/send-email).
message_idtodos, salvo algunos open/clickMessage-ID asignado al email. Los open/click registrados por el tracking propio de RQE (pixel/redirect) no lo incluyen — usa activity_id como identificador estable.
recipienttodosEmail del destinatario.
event_typetodosTipo lowercase (delivery, bounce, open, click, ...). Equivale a event sin email..
event_timestamptodosISO timestamp del evento real (distinto del top-level timestamp que es cuando RQE lo despachó).
bounce_typebouncePermanent o Transient.
bounce_subtypebounceGeneral, NoEmail, Suppressed, etc.
complaint_feedback_typecomplaintabuse, auth-failure, fraud, not-spam, other, virus.
url_clickedclickURL original a la que el destinatario hizo click.

Nota: los eventos email.send y email.delivery no incluyen un campo delivered_at separado — usa event_timestamp.

Headers de la request

RQE hace POST a tu URL con:

HeaderValor
Content-Typeapplication/json
User-AgentReallyQuickEmails-Webhook/1.0
X-RQE-Signaturesha256=<hmac_hex> — HMAC-SHA256 del body raw, secret = tu API key de producción (sk_proj_*), incluso para eventos de envíos test
X-RQE-Environmentlive, dev, o el nombre del environment custom si aplica. Solo en eventos outbound — el POST de email.inbound no incluye este header

Verificación HMAC

javascript
const crypto = require('crypto');
const expected = 'sha256=' + crypto
  .createHmac('sha256', apiKey)
  .update(rawBody)
  .digest('hex');
if (req.headers['x-rqe-signature'] !== expected) {
  return res.status(401).send('Invalid signature');
}

Importante: verifica la firma sobre el body raw, antes de cualquier parser JSON. Si el body cambia (espacios, orden de keys), el HMAC no va a coincidir.

Routing live / test / environment

Cada proyecto tiene URLs configurables:

URLRecibe
webhook_url (Producción)Todos los eventos outbound — live y test
webhook_url_dev (Desarrollo)Todos los eventos outbound — live y test
webhook_environments[<key>]Solo eventos de envíos que incluyeron environment: "<key>" en el body (override: las demás URLs no reciben ese envío)

Sin environment custom, cada evento outbound se entrega a todas las URLs configuradas (Producción y Desarrollo a la vez) — el routing no filtra por modo del envío. El header X-RQE-Environment indica el slot de cada POST (live / dev) y el flag is_test del payload te dice si el envío usó sk_test_*.

Si configuras inbound_webhook_url / inbound_webhook_url_dev, esas URLs reemplazan a webhook_url / webhook_url_dev y reciben todos los eventos (outbound + inbound) — modelo "una URL recibe todo".

Los webhooks inbound (replies) se entregan a una sola URL según el modo del envío original (ver fallback abajo). El override por environment usa inbound_webhook_environments[<key>].

Fallback automático

Aplica solo a email.inbound — los eventos outbound no tienen fallback porque van a todas las URLs configuradas. RQE usa la primera URL no vacía de esta cadena:

  • Envío original con sk_test_*: inbound_webhook_url_devwebhook_url_devinbound_webhook_urlwebhook_url
  • Envío original live: inbound_webhook_urlwebhook_urlinbound_webhook_url_devwebhook_url_dev

Si ninguna URL está configurada (outbound o inbound), el evento no se entrega.

Para environments custom, no hay fallback — si el environment declarado en el envío no está configurado, el envío entero falla con 400 ENVIRONMENT_NOT_CONFIGURED. Ver Webhook environments.

is_test en el payload

json
{
  "event": "email.delivery",
  "is_test": true,
  "data": { "...": "..." }
}

Como los eventos outbound llegan a todas las URLs configuradas, basta con configurar solo webhook_url para recibir todo en una sola URL — distingue el tráfico test con el flag is_test.

Inbound (replies con adjuntos)

Cuando un destinatario responde tu email, RQE captura la respuesta y la dispara como evento email.inbound.

Cuándo dispara

Solo cuando el email outbound original fue enviado vía API (POST /v1/send-email, POST /send-email o POST /v1/send-batch). En ese caso el Reply-To del email lleva un token único:

text
Reply-To: "Tu Empresa" <r-{token}@rqe.inbound.reallyquickemails.com>

Los clientes de email (Gmail, Outlook, Apple Mail) muestran el nombre del remitente en lugar de la dirección técnica. Cuando el destinatario responde, la respuesta se enruta automáticamente a RQE y dispara el webhook.

No dispara para envíos desde la UI de RQE (campañas, automatizaciones, "enviar prueba") — esos usan el Reply-To del sender humano para que reciba en su inbox.

Body del payload inbound

json
{
  "event": "email.inbound",
  "timestamp": "2026-04-28T22:45:32.299Z",
  "project_id": "uuid",
  "is_test": false,
  "data": {
    "message_id": "<gmail-message-id@mail.gmail.com>",
    "thread_id": "uuid",
    "in_reply_to": "<original-message-id@us-east-1.amazonses.com>",
    "references": ["<...>"],
    "from": { "email": "cliente@empresa.com", "name": "Cliente Externo" },
    "to": [
      { "email": "r-Rbxiu6RC@rqe.inbound.reallyquickemails.com", "name": null }
    ],
    "cc": [],
    "subject": "Re: Asunto original",
    "text_body": "respuesta plana del cliente",
    "html_body": "<div>respuesta html</div>",
    "date": "2026-04-28T23:30:49.000Z",
    "attachments": [
      {
        "filename": "factura.pdf",
        "content_type": "application/pdf",
        "size": 50826,
        "storage_key": "...",
        "download_url": "https://...?token=..."
      }
    ],
    "return_path_parsed": null,
    "original_outbound": {
      "message_id": "<original-message-id@us-east-1.amazonses.com>",
      "activity_id": "uuid",
      "campaign_id": null,
      "email_type": null,
      "thread_id": "uuid"
    }
  }
}

Igual que en los eventos outbound, el payload incluye is_test y, si el envío original usó un environment custom, también environment.

Campos clave

CampoDescripción
data.fromQuien envió la respuesta (cliente externo).
data.to[]Dirección token a la que respondió.
data.text_body / data.html_bodyCuerpo de la respuesta.
data.attachments[]Adjuntos del reply. Cada uno incluye filename, content_type, size, storage_key y download_url firmada con TTL 7 días. Descarga y persiste si necesitas retención mayor.
data.return_path_parsed{ project_id, activity_id } si la dirección respondida referencia directamente el envío original; null en caso contrario.
data.original_outboundReferencia al email original que disparó la conversación: message_id, activity_id, campaign_id, email_type, thread_id (null si no se pudo resolver).

Límite de tamaño

150 KB total por reply (incluyendo adjuntos en MIME base64). Replies que excedan ese tamaño rebotan con "Message length exceeds limit set by recipient". Workaround: pídele al contacto que comparta archivos grandes vía link Drive/WeTransfer.

Retry y respuesta esperada

StatusComportamiento
200299Éxito. RQE marca el dispatch como exitoso.
Otro / timeout / error de redRQE reintenta con backoff exponencial (30s → 1m → 2m → 4m). Después de 5 intentos fallidos, no se reintenta más.
  • Timeout: 10 segundos por intento.
  • Intentos (eventos outbound): 5 en total — 1 inicial + 4 reintentos con backoff exponencial.
  • email.inbound: un solo intento, sin reintentos.
  • Idempotencia: los retries pueden re-entregar el mismo evento; si entregas a ambas URLs y solo una falla, el reintento re-entrega a las dos. Usa el par (event, data.activity_id, data.event_timestamp) para deduplicar lado-cliente.

Próximos pasos

  • Tracking — qué eventos disparan webhook y cómo se generan.
  • Test mode — separar tráfico dev/prod vía sk_test_*.
  • Webhook environments — N URLs por proyecto vía campo environment.
  • Send email — cómo disparar emails que generen estos eventos.