Ir a la documentación
PHP

Endpoints

`webhooks->list`, `listAll`, `iterate`, `get`, `create`, `update`, `delete`, `rotateSecret`, `test`, `getDelivery` y `replayDelivery`, y los registros de entregas y de actividad.

Todos los métodos

webhooks.php
use OpenEmail\Constants\WebhookEvents; $endpoint = $client->webhooks->create([    'url' => 'https://acme.com/hooks/mail',    'eventTypes' => [WebhookEvents::EMAIL_SENT, WebhookEvents::EMAIL_BOUNCED],    'description' => 'Billing service',]); file_put_contents('.openemail-webhook-secret', $endpoint['secret']); $client->webhooks->list();$client->webhooks->get($endpoint['id']);$client->webhooks->update($endpoint['id'], ['enabled' => false]);$client->webhooks->test($endpoint['id']); foreach ($client->webhooks->listDeliveries($endpoint['id'], limit: 1) as $latest) {    $client->webhooks->getDelivery($endpoint['id'], $latest['id']);    $client->webhooks->replayDelivery($endpoint['id'], $latest['id']);} $rotated = $client->webhooks->rotateSecret($endpoint['id']);file_put_contents('.openemail-webhook-secret', $rotated['secret']); $client->webhooks->delete($endpoint['id']);

create es la ÚNICA ocasión en que se devuelve el secreto, aparte de rotateSecret. 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.

list devuelve una OpenEmail\Result\Page, listAll devuelve todos los endpoints en un solo array, e iterate devuelve un Generator que entrega un endpoint cada vez. create y update aceptan el cuerpo como un solo array con los nombres de la API, y cada endpoint vuelve como un array con claves en camelCase.

rotateSecret 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\Constants\WebhookEvents nombra cada evento como una constante, y WebhookEvents::values() los lista, para que puedas mostrar la lista sin hacer ninguna solicitud. webhooks->listEvents devuelve los mismos nombres con una etiqueta para cada uno, más los límites a los que está sujeto un endpoint en maxEndpoints, maxAddresses y maxDomains. 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 null 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 null, 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.php
$result = $client->webhooks->test('whe_3f9c2a7b1e4d8f60a5c7b92d');echo $result['delivery']['status'], ' ', $result['delivery']['responseCode'] ?? 'no response', PHP_EOL; foreach ($client->webhooks->iterateDeliveries('whe_3f9c2a7b1e4d8f60a5c7b92d') as $delivery) {    echo $delivery['eventType'], ' ', $delivery['status'], ' ', $delivery['responseCode'] ?? '-', ' ', $delivery['error'] ?? '', PHP_EOL;}

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 $result['delivery']['status'] y no según si la llamada lanzó una excepción. 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 null 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.php
$detail = $client->webhooks->getDelivery('whe_3f9c2a7b1e4d8f60a5c7b92d', 'whd_8c1e4a7f2b9d3e6a0c5f1b28');echo json_encode($detail['payload'], JSON_THROW_ON_ERROR), PHP_EOL;echo $detail['responseBody'] ?? 'no answer', ' ', $detail['replayRefusal']['code'] ?? 'replayable', PHP_EOL; $replay = $client->webhooks->replayDelivery('whe_3f9c2a7b1e4d8f60a5c7b92d', 'whd_8c1e4a7f2b9d3e6a0c5f1b28');echo $replay['delivery']['status'], ' ', $replay['delivery']['responseCode'] ?? 'no response', PHP_EOL;

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.

  • replayDelivery 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, replayDelivery 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 getDelivery, 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). Cada uno es una ConflictException, y OpenEmail\Constants\WebhookReplayErrorCodes nombra los códigos. getDelivery informa de esa respuesta por adelantado como replayRefusal, null cuando un reenvío seguiría adelante y, si no, un array con code y message.

El paquete nunca reintenta replayDelivery 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, carrier-grade NAT, link-local, multicast o unique 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
Qué eventos llegan a este endpoint: cualquiera de los valores de `OpenEmail\Constants\WebhookEvents`. `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 null. Omite la clave en lugar de pasar null: el cliente envía un null tal cual, y `create` lo rechaza con un 422.
addressAllowlistarray
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
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.
apiKeystring
Un argumento nombrado junto al array y no una clave dentro de él: crea el endpoint con esta clave de API en lugar de la del cliente.

Respuesta: el endpoint creado

Un array con claves en camelCase. 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`, `rotateSecret`, `test`, `listDeliveries`, `listAllDeliveries`, `iterateDeliveries`, `getDelivery` y `replayDelivery`.
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 cadena que enviaste.
descriptionstring or null
La etiqueta que le pusiste, o null si no pusiste ninguna. Un `update` que envía `'description' => null` la borra.
eventTypesarray
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.
enabledbool
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 null
Cuándo desactivó el servidor el endpoint tras 100 entregas fallidas seguidas. null mientras está activo, y cuando lo desactivaste tú.
disabledReasonstring or null
Por qué lo desactivó el servidor. null siempre que `disabledAt` es null.
consecutiveFailuresint
Las entregas fallidas seguidas. Cualquier evento entregado lo pone a 0, y también `update` con `enabled` en true.
addressAllowlistarray
Las direcciones individuales de las que se entera este endpoint.
domainAllowlistarray
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 null
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 `listDeliveries` te dice cómo fue. null hasta el primer intento y, por tanto, siempre null 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::verifyWebhookSignature`, prefijo incluido. La devuelven `create` y `rotateSecret`, y nada más. Una lectura nunca la repite, así que guárdala ahora. Un secreto perdido solo puede reemplazarse con `rotateSecret`, que invalida el antiguo de inmediato.

Filtrar los registros

webhook_logs.php
$failed = $client->webhooks->listWorkspaceDeliveries(status: 'failed', since: new \DateTimeImmutable('-1 day')); foreach ($failed as $delivery) {    echo $delivery['endpointId'], ' ', $delivery['eventType'], ' ', $delivery['responseCode'] ?? '-', PHP_EOL;} $history = $client->webhooks->listActivity('whe_3f9c2a7b1e4d8f60a5c7b92d'); foreach ($history as $change) {    echo $change['type'], ' ', $change['actor']['label'] ?? 'OpenEmail', PHP_EOL;}

listDeliveries lee un endpoint y listWorkspaceDeliveries todos los endpoints, o los que nombra endpointIds:, como array o como una sola cadena separada por comas, y ambos aceptan status: (delivered o failed), since: y until:, los filtros de la pestaña Entregas de la consola. listActivity y listWorkspaceActivity 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 listAll y otra iterate, como listAllDeliveries e iterateDeliveries, 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 DateTimeInterface o una cadena ISO 8601, y una cadena con una fecha sin hora significa medianoche UTC de ese día. until: tiene que ser posterior a since:, o la llamada lanza una InvalidRequestException con errorCode igual a invalid_parameter.