Ir a la documentación
Ruby

Endpoints

`webhooks.list`, `list_all`, `iterate`, `get`, `create`, `update`, `delete`, `rotate_secret`, `test`, `get_delivery` y `replay_delivery`, y los registros de entregas y de actividad.

Todos los métodos

webhooks.rb
endpoint = client.webhooks.create(  url: "https://acme.com/hooks/mail",  eventTypes: ["email.sent", "email.bounced"],  description: "Billing service") File.write(".openemail-webhook-secret", endpoint[:secret]) client.webhooks.listclient.webhooks.get(endpoint[:id])client.webhooks.update(endpoint[:id], enabled: false)client.webhooks.test(endpoint[:id])latest = client.webhooks.list_deliveries(endpoint[:id], limit: 1).items.firstclient.webhooks.get_delivery(endpoint[:id], latest[:id])client.webhooks.replay_delivery(endpoint[:id], latest[:id])rotated = client.webhooks.rotate_secret(endpoint[:id])File.write(".openemail-webhook-secret", rotated[:secret])client.webhooks.delete(endpoint[:id])

create es la ÚNICA ocasión en que se devuelve el secreto, aparte de rotate_secret. Una lectura nunca lo repite, así que guárdalo antes de hacer cualquier otra cosa. Omite eventTypes para obtener el conjunto predeterminado, todos los eventos email.* salvo email.replied. email.replied, domain.*, suppression.*, file.* y form.* solo llegan a un endpoint cuando este los nombra.

rotate_secret no tiene ventana de solapamiento. El secreto antiguo deja de funcionar de inmediato, así que despliega el nuevo antes de rotar. Nunca se reintenta automáticamente: un reintento rotaría por segunda vez e invalidaría el secreto que devolvió el primer intento.

create tampoco se reintenta, así que un fallo de red puede dejar un endpoint creado con un secreto que nunca viste. Consulta list antes de volver a crearlo. Un espacio de trabajo admite 10 endpoints por defecto, y el siguiente por encima del límite es un 422 workspace_limit_reached.

A qué puedes suscribirte

OpenEmail::WEBHOOK_EVENTS es un Hash congelado con todos los nombres de eventos, para que puedas mostrar la lista sin hacer ninguna solicitud, y webhooks.list_events devuelve los mismos nombres con una frase para cada uno, más los límites a los que está sujeto un endpoint. Los eventos son eventos del **buzón**, no de esta API: email.received se dispara con el correo que llega a la aplicación, y email.sent se dispara con un mensaje que envió el redactor. Suscribirse no es lo mismo que observar tu propio tráfico de API.

file.uploaded se dispara cuando se pone un archivo en la página Archivos, y file.deleted cuando se elimina uno. Su data contiene fileId, filename, mimeType, sizeBytes, direction, to, threadId, messageId, y uploadedAt o deletedAt. to es la dirección a la que pertenece el archivo, o nil para un archivo que pertenece a todo el espacio de trabajo.

Los eventos de archivo no están en el conjunto predeterminado, así que un endpoint solo los recibe cuando los nombra en eventTypes. Un endpoint limitado a algunas direcciones solo se entera de los archivos de esas direcciones, así que una subida para todo el espacio de trabajo, con to a nil, no se le envía.

form.submitted se dispara cuando alguien se suscribe mediante uno de tus formularios, y form.confirmed cuando una suscripción pendiente se une a las audiencias, porque la persona abrió el enlace de confirmación o porque la aprobaste. El data de form.submitted contiene formId, formName, submissionId, email, status, answers, audienceIds, sourceUrl y submittedAt. El data de form.confirmed contiene formId, formName, submissionId, email, audienceIds, via, que es link o approval, y confirmedAt.

Una suscripción en un formulario sin doble opt-in envía form.submitted con status added y ningún form.confirmed, así que trata esa combinación como el momento en que alguien se une. Quien se suscribe de nuevo antes de confirmar conserva el mismo submissionId, y form.submitted se vuelve a enviar solo si sus respuestas cambiaron. Los eventos de formulario no están en el conjunto predeterminado, y un endpoint limitado a algunas direcciones nunca los recibe, porque las suscripciones pertenecen a todo el espacio de trabajo.

Comprobar que funciona

webhook_test.rb
result = client.webhooks.test("whe_3f9c2a7b1e4d8f60a5c7b92d")puts result.dig(:delivery, :status), result.dig(:delivery, :responseCode) client.webhooks.iterate_deliveries("whe_3f9c2a7b1e4d8f60a5c7b92d") do |delivery|  puts "#{delivery[:eventType]} #{delivery[:status]} #{delivery[:responseCode]} #{delivery[:error]}"end

test envía por POST un evento email.sent sintético y firmado, y espera a que termine el intento. Devuelve normalmente responda lo que responda tu receptor, así que ramifica según delivery[:status] y no según si la llamada lanzó un error. Un 4xx es una respuesta útil: la URL es accesible y el rechazo vino de tu propio gestor, a menudo de su comprobación de firma.

Un responseCode nil significa que no hubo respuesta alguna (DNS, TLS, un tiempo de espera agotado), que es un hecho distinto de una respuesta que dijo 0. Cada fila lleva attempt y maxAttempts, así que varias filas pueden describir un mismo evento: el eventId compartido entre ellas identifica el evento, y el número de intento identifica cada envío. nextAttemptAt indica cuándo toca el reintento automático que sigue a una fila.

Volver a enviarlo

webhook_replay.rb
detail = client.webhooks.get_delivery("whe_3f9c2a7b1e4d8f60a5c7b92d", "whd_8c1e4a7f2b9d3e6a0c5f1b28")p detail[:payload], detail[:responseBody], detail[:replayRefusal] replay = client.webhooks.replay_delivery("whe_3f9c2a7b1e4d8f60a5c7b92d", "whd_8c1e4a7f2b9d3e6a0c5f1b28")p replay.dig(:delivery, :status), replay.dig(:delivery, :responseCode)

Una entrega que sigue fallando se intenta hasta 8 veces: en el momento, y después al cabo de 1 minuto, 5 minutos, 30 minutos, 2 horas, 5 horas, 10 horas y 10 horas, unas 27 horas y media en total. Solo se repite un fallo que merezca repetirse: sin respuesta, 408, 425, 429 o un 5xx. Un reenvío manda de nuevo el evento guardado con el mismo id, type, createdAt y data, así que un receptor que descarta los ids que ya ha procesado lo trata como el evento que ya conoce. Solo la firma es nueva.

  • replay_delivery envía un evento ahora y devuelve lo que respondió tu servidor. Funciona también con un intento entregado y nunca se reintenta. Antes de enviar, se pausan los reintentos automáticos de ese evento que aún no han empezado: siguen cancelados si el reenvío se entrega y se reanudan según su calendario si falla.
  • Si en ese momento se está enviando un reintento automático del mismo evento, replay_delivery no envía nada y lanza un 409 retry_in_progress, y mientras otro reenvío suyo se siga enviando lanza un 409 replay_in_progress, de modo que tu receptor nunca recibe dos copias a la vez, ni siquiera de dos reenvíos hechos en el mismo instante. Espera unos segundos y consulta get_delivery, porque ese reintento o reenvío puede entregarlo. El reenvío es de un evento cada vez: ninguna llamada vuelve a enviar todas las entregas fallidas.
  • También lanza un 409 para un endpoint desactivado (webhook_disabled), un evento que el endpoint ya no escucha (event_not_subscribed) o ya no cubre (event_out_of_scope), y un intento sin evento guardado (delivery_not_replayable). get_delivery informa de esa respuesta por adelantado como replayRefusal.

La gema nunca reintenta replay_delivery por su cuenta, porque un reintento tras una respuesta perdida volvería a enviar el evento.

Parámetros: webhooks.create

urlStringobligatorio
A dónde se hace POST de las entregas. Solo HTTPS, y el host no puede ser `localhost`, un nombre `.localhost`, `.local` o `.internal`, ni un literal de IP de loopback, privada, CGNAT o link-local. Esto es una petición desde el servidor a una dirección que tú indicas, así que esos casos son un 422 `invalid_webhook_url` sobre `url`. La comprobación lee el nombre de host tal como está escrito, y cada entrega vuelve a resolver el host y se niega a enviar a una dirección de alguno de esos rangos. Las entregas nunca siguen redirecciones, así que registra la dirección final. Lo que se almacena es la serialización que hace el analizador de URL de lo que enviaste, así que `https://acme.com` se lee de vuelta como `https://acme.com/`.
eventTypesArray<String>
Qué eventos llegan a este endpoint: cualquiera de los valores de `OpenEmail::WEBHOOK_EVENTS`. `create` limita el Array al número de eventos que existen, así que uno más es un 422 sobre `eventTypes`, y `update` no lo limita. Solo se limita la longitud, y un nombre repetido se almacena y se lee de vuelta exactamente como lo enviaste. Omitido o vacío se almacena como una lista vacía, y por eso se lee de vuelta como `["*"]`, y significa todos los eventos `email.*` salvo `email.replied`, catorce hoy, y nunca las familias de dominio, de supresión, de archivos o de formularios. Una familia añadida más tarde nunca llega a un endpoint que no la nombró, de modo que una integración no puede empezar a recibir una forma que nunca ha visto por culpa de una publicación.
descriptionString
Una etiqueta para el endpoint, de 200 caracteres como máximo, para que una lista de webhooks se lea como nombres y no como una columna de URLs. Si se omite, se almacena y se devuelve como nil.
addressAllowlistArray<String>
Las direcciones individuales de las que se entera este endpoint. Un evento se entrega cuando la dirección a la que se refiere está en esta lista, o cuando su dominio está en `domainAllowlist`. Deja ambas vacías y el endpoint se entera de todas las direcciones que posee el espacio de trabajo. Como máximo 50, y una dirección que este espacio de trabajo no posee es un 422 `invalid_parameter`.
domainAllowlistArray<String>
Los dominios enteros de los que se entera este endpoint, incluidas las direcciones que se les añadan más tarde. Un dominio también lleva sus propios eventos `domain.*`. Como máximo 25.
api_keyString
Crea el endpoint con esta clave en lugar de la del cliente.

Respuesta: el endpoint creado

Un Hash con claves Symbol. get, list y update devuelven la misma forma sin secret.

objectString
Siempre `webhook`, el mismo discriminador que devuelve una lectura normal, porque el secreto es una clave extra sobre la forma habitual y no un tipo de objeto propio. Que `secret` esté presente lo decide el método que llamaste, no este campo.
idString
El identificador del endpoint: `whe_` seguido de 24 caracteres hexadecimales. Todas las demás llamadas de webhook lo reciben: `get`, `update`, `delete`, `rotate_secret`, `test`, `list_deliveries`, `list_all_deliveries`, `iterate_deliveries`, `get_delivery` y `replay_delivery`.
urlString
El endpoint tal como está almacenado, tras superar las comprobaciones de HTTPS y de hosts bloqueados. Es la URL analizada y vuelta a serializar, así que compara contra este valor y no contra la String que enviaste.
descriptionString or nil
La etiqueta que le pusiste, o nil si no pusiste ninguna. Un `update` que envía `description: nil` la borra.
eventTypesArray<String>
Los eventos suscritos, o `["*"]` cuando el endpoint no nombró ninguno. `["*"]` es como se representa al leer una lista almacenada vacía y no puede enviarse de vuelta, y representa los catorce eventos de mensaje, no el catálogo completo. `create` y `update` solo aceptan los nombres literales de los eventos.
enabledBoolean
Si se intentan las entregas. Un endpoint deshabilitado se omite al despachar los eventos y conserva su secreto y su historial de entregas. Aquí siempre es true, ya que solo `update` acepta `enabled`.
disabledAtString or nil
Cuándo desactivó el servidor el endpoint tras 100 entregas fallidas seguidas. nil mientras está activo, y cuando lo desactivaste tú.
disabledReasonString or nil
Por qué lo desactivó el servidor. nil siempre que `disabledAt` es nil.
consecutiveFailuresInteger
Las entregas fallidas seguidas. Cualquier evento entregado lo pone a 0, y también `update` con `enabled: true`.
addressAllowlistArray<String>
Las direcciones individuales de las que se entera este endpoint.
domainAllowlistArray<String>
Los dominios enteros de los que se entera este endpoint. Con ambas listas vacías, son todas las direcciones que posee el espacio de trabajo.
lastDeliveryAtString or nil
Marca de tiempo ISO 8601 del último INTENTO de entrega, no del último éxito. También se registra tras un POST fallido, así que te dice que se probó el endpoint, y `list_deliveries` te dice cómo fue. nil hasta el primer intento y, por tanto, siempre nil en `create`.
createdAtString
Marca de tiempo ISO 8601 de cuándo se registró el endpoint. `list` devuelve los endpoints del más reciente al más antiguo según este campo.
secretString
La clave HMAC-SHA-256 que firma el `X-OpenEmail-Signature` de cada entrega: `whsec_` seguido de 43 caracteres base64url, y lo que le pasas a `OpenEmail.verify_webhook_signature`, prefijo incluido. La devuelven `create` y `rotate_secret`, y nada más. Una lectura nunca la repite, así que guárdala ahora. Un secreto perdido solo puede reemplazarse con `rotate_secret`, que invalida el antiguo de inmediato.

Filtrar los registros

webhook_logs.rb
failed = client.webhooks.list_workspace_deliveries(status: "failed", since: Time.now - 86_400)p failed.items.map { |delivery| [delivery[:endpointId], delivery[:eventType], delivery[:responseCode]] } history = client.webhooks.list_activity("whe_3f9c2a7b1e4d8f60a5c7b92d")p history.items.map { |change| [change[:type], change.dig(:actor, :label)] }

list_deliveries lee un endpoint y list_workspace_deliveries todos los endpoints, o los que nombra endpoint_ids:, y ambos aceptan status:, since: y until:, los filtros de la pestaña Entregas de la consola. list_activity y list_workspace_activity leen el registro de auditoría: quién creó, cambió, activó o desactivó, rotó, probó, reenvió o eliminó qué. Cada uno tiene al lado una versión list_all_ y otra iterate_, y cada fila del registro del espacio de trabajo lleva endpointId. webhooks.stats devuelve las cifras de la pestaña Analíticas para el periodo que elijas.

since: y until: aceptan un Time, un DateTime o un instante ISO 8601 como String, y una Date de Ruby significa medianoche UTC de ese día. until es una palabra clave de Ruby, pero funciona como argumento nombrado igual que cualquier otro: list_deliveries(id, since: start, until: finish).