Skip to main content
RQE dispara un webhook por cada evento de un email: aceptación, entrega, rebote, queja, apertura, click, supresión y respuesta del destinatario.

Cómo recibir webhooks

1

Configura la URL en el dashboard

Abre tu proyecto y ve a Configuración → Integraciones → Webhooks. Pega la URL pública de tu endpoint. Las URLs por proyecto y qué recibe cada una están en Routing live / test / environment.
2

Expón tu endpoint local

En desarrollo, crea un túnel a tu servidor local con ngrok (ngrok http 3000) y usa la URL pública generada como URL del webhook.
3

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.
4

Responde 200

Devuelve un status 2xx cuanto antes. Cualquier otro status, timeout o error de red activa reintentos. Ver Retry y respuesta esperada.
5

Pasa a producción

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

Eventos

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. Solo cambia data.

Campos de data

Los eventos email.send y email.delivery no incluyen un campo delivered_at separado — usa event_timestamp.

email.suppressed

Se emite cuando envías vía API a una dirección que está en la lista de supresión del proyecto (rebote duro o queja previos): el correo se bloquea antes de salir y la respuesta de /v1/send-email ya lo indica con suppressed: true (ver Destinatario suprimido). Úsalo para marcar la dirección en tu sistema y dejar de intentar. Particularidades vs los demás eventos outbound:
  • Solo se emite para envíos vía API — campañas y automatizaciones no lo disparan.
  • Su payload es reducido: el top-level no incluye is_test ni environment, y no hay message_id — el correo nunca salió. data trae email, activity_id, email_type, suppressed_at y el motivo (suppression_reason, suppression_source, suppression_description).

suppression.added / suppression.removed

email.suppressed avisa cuando intentas enviar a una dirección ya suprimida. Estos dos avisan en el momento en que la dirección entra o sale de la lista, sin esperar a un envío. Es la diferencia que importa si mantienes tu propio CRM: sin ellos, un contacto se te muere en silencio y solo te enteras en el siguiente intento, que puede ser semanas después. suppression.added se emite cuando RQE suprime automáticamente una dirección tras un rebote duro o una queja de spam.
En una queja, reason es complaint y en lugar de los campos de rebote llega complaint_feedback_type. suppression.removed se emite cuando una dirección se quita de la lista desde el dashboard o la API — útil porque puede haberla reactivado otra persona de tu equipo, no tu sistema.
suppression.added no se emite para los rebotes de subtipo OnAccountSuppressionList: ese es el eco de la lista del proveedor, no un rebote nuevo, y no genera una supresión en tu proyecto.En una remoción masiva se emite un evento por dirección hasta un tope de 200; si lo superas queda registrado en los logs.

domain.send_ready

Se emite una sola vez, cuando el dominio pasa a poder enviar. Llega antes que domain.verified: SES habilita el envío en cuanto reconoce el dominio, mientras DKIM puede seguir propagando un rato más. Sirve para desbloquear al remitente sin tener que fingir que la autenticación ya terminó. Si automatizas altas de dominio, este es el evento que te deja habilitar el envío en tu propia UI; domain.verified llega después y confirma que además hay firma DKIM alineada.
Un dominio que puede enviar pero todavía no tiene DKIM verificado entrega peor: sin firma alineada, DMARC no pasa por DKIM. Habilita el envío si lo necesitas, pero espera a domain.verified antes de mandar volumen.

domain.verified / domain.failed

Se emiten cuando un dominio de envío cambia de estado de verificación. Son la alternativa a consultar GET /domains/{domain}/status en bucle: si automatizas el alta de dominios, escucha estos eventos en lugar de pollear.
Solo en la transición. El estado de los dominios pending se re-consulta periódicamente, pero el webhook se emite una única vez, cuando el estado realmente cambia. Un dominio que sigue verified no vuelve a emitir. previous_verification_status te dice desde qué estado venía.
domain.failed llega con la misma forma, con verification_status: "failed" y can_send: false. Revisa details para saber qué parte falló (domain_verification o dkim_verification) y llama a POST /domains/{domain}/recreate si DKIM quedó atascado.

Headers de la request

RQE hace POST a tu URL con:

Verificación HMAC

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: 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); el flag is_test del payload 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) van 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

Como los eventos outbound llegan a todas las URLs configuradas, basta configurar 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 email.inbound.

Cuándo dispara

Solo cuando el email outbound original se envió vía API (POST /v1/send-email, POST /send-email o POST /v1/send-batch). En ese caso el Reply-To lleva un token único:
Los clientes (Gmail, Outlook, Apple Mail) muestran el nombre del remitente, no la dirección técnica. Al responder, la respuesta llega 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.

Reply domain branded (opcional, sin token)

Un proyecto puede configurar un dominio de reply propio (reply.tudominio.com) para que el Reply-To salga limpio y branded, sin el token codificado:
En vez del token, el dominio identifica tu proyecto y el Message-ID identifica el hilo (headers In-Reply-To / References). El evento email.inbound que recibes es idéntico: sigues leyendo activity_id y thread_id del payload, no la dirección. El token siempre fue plomería interna de RQE, no algo que tu integración parsee — así que activar un reply domain no cambia tu código.
Se habilita por proyecto publicando 2 registros DNS (un TXT de verificación SES en _amazonses.reply.tudominio.com + un MX reply.tudominio.cominbound-smtp.us-east-1.amazonaws.com) y coordinando con RQE. La configuración self-service está en camino. Sin reply domain configurado, se usa el token de siempre.
Con reply domain el hilo se resuelve por los headers de Message-ID. En casos raros —un cliente de correo que borra esos headers, o un correo nuevo enviado directo a la dirección que no es una respuesta— el evento llega con thread_id: null. Trátalo como un inbound nuevo.

Body del payload inbound

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

Campos clave

Límite de tamaño

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

Retry y respuesta esperada

  • 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.