Ir a la documentación
Python

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

usage.py
from acme.secrets import store endpoint = client.webhooks.create({    'url': 'https://acme.com/hooks/mail',    'eventTypes': ['email.sent', 'email.bounced'],    'description': 'Billing service',}) store(endpoint['secret']) client.webhooks.list()client.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'][0]client.webhooks.get_delivery(endpoint['id'], latest['id'])client.webhooks.replay_delivery(endpoint['id'], latest['id'])rotated = client.webhooks.rotate_secret(endpoint['id'])store(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.

A qué puedes suscribirte

WEBHOOK_EVENTS se exporta para que puedas mostrar la lista. 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. Sus datos son FileEventData: 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. form.submitted lleva FormSubmittedEventData: formId, formName, submissionId, email, status, answers, audienceIds, sourceUrl y submittedAt. form.confirmed lleva FormConfirmedEventData: 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.

Cada una de estas formas de datos es un TypedDict en openemail.types. Anota un evento verificado como WebhookPayload[FileEventData], por ejemplo, y un comprobador de tipos sabrá qué contiene event['data'].

Comprobar que funciona

webhook_test.py
result = client.webhooks.test('whe_…')delivery = result['delivery'] if delivery is not None:    print(delivery['status'], delivery['responseCode']) for d in client.webhooks.iterate_deliveries('whe_…'):    print(d['eventType'], d['status'], d['responseCode'], d['error'])

Un responseCode de None 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.py
detail = client.webhooks.get_delivery('whe_…', 'whd_…')print(detail['payload'], detail['responseBody'], detail['replayRefusal']) replay = client.webhooks.replay_delivery('whe_…', 'whd_…')print(replay['delivery']['status'], replay['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 se rechaza con 409 retry_in_progress, y mientras otro reenvío suyo se siga enviando se rechaza con 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 rechaza con 409 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.

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

Cada rechazo lanza OpenEmailApiError con status 409, is_conflict verdadero y el motivo como code, uno de los valores de WEBHOOK_REPLAY_ERROR_CODES.

Parámetros: webhooks.create

urlstrobligatorio
A dónde se hace POST de las entregas. Solo HTTPS, y el host no puede ser `localhost`, un nombre `.localhost`/`.local`/`.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 sobre `url`; la comprobación lee el nombre de host tal como está escrito y nunca resuelve DNS. 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/`.
eventTypeslist[WebhookEvent]
Qué eventos llegan a este endpoint: cualquiera de los nombres de `WEBHOOK_EVENTS`. `POST /webhooks` limita el array al número de eventos que existen, así que uno más es un 422 sobre `eventTypes`; `PATCH` 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 o de archivos. 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.
descriptionstr
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.

Respuesta: CreatedWebhookResource

objectLiteral['webhook']
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.
idstr
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`.
urlstr
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.
descriptionstr | None
La etiqueta que le pusiste, o null si no pusiste ninguna. Un `update` que envía un null explícito la borra de vuelta a null.
eventTypeslist[WebhookEvent] | ['*']
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 `WebhookCreate` no tiene `enabled` y solo `WebhookPatch` lo tiene.
lastDeliveryAtstr | None
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. Null hasta el primer intento y, por tanto, siempre null en `create`.
createdAtstr
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.
secretstr
La clave HMAC-SHA-256 que firma el `X-OpenEmail-Signature` de cada entrega: `whsec_` seguido de 32 bytes aleatorios en base64url, y lo que le pasas a `verify_webhook_signature`. La devuelven `create` y `rotate_secret`, y nada más. Una lectura nunca lo repite, así que guárdalo ahora; un secreto perdido solo puede reemplazarse con `rotate_secret`, que invalida el antiguo de inmediato.

Filtrar los registros

webhook_logs.py
from datetime import datetime, timedelta, timezone failed = client.webhooks.list_workspace_deliveries(    status='failed',    since=datetime.now(timezone.utc) - timedelta(days=1),)print(len(failed['items'])) history = client.webhooks.list_activity('whe_…')print([(change['type'], change['actor']['label'] if change['actor'] else None) for change in history['items']])

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 un list_all_… y un iterate_… al lado, y cada fila del registro del espacio de trabajo lleva endpointId.

since= y until= aceptan un datetime o una cadena ISO 8601. Un datetime sin zona horaria se interpreta como hora local y se convierte a UTC, así que pasa uno con zona horaria, como arriba.

Referencia