Saltar para a documentação
Python

Endpoints

`webhooks.list`, `list_all`, `iterate`, `get`, `create`, `update`, `delete`, `rotate_secret`, `test`, `get_delivery` e `replay_delivery`, e os registos de entregas e de atividade.

Todos os 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 é a ÚNICA altura em que o segredo é devolvido, além de rotate_secret. Uma leitura nunca o repete, por isso guarde-o antes de fazer mais alguma coisa. Omita eventTypes para o conjunto por omissão, todos os eventos email.* exceto email.replied. email.replied, domain.*, suppression.*, file.* e form.* só chegam a um endpoint quando este os nomeia.

rotate_secret não tem janela de sobreposição. O segredo antigo deixa de funcionar imediatamente, por isso coloque o novo em produção antes de rodar. Nunca é repetido automaticamente: uma repetição rodaria uma segunda vez e invalidaria o segredo que a primeira tentativa devolveu.

A que pode subscrever

WEBHOOK_EVENTS é exportado para que possa desenhar a lista. Os eventos são eventos da **caixa de correio**, não desta API: email.received dispara para o correio que chega à aplicação, e email.sent dispara para uma mensagem enviada pelo compositor. Subscrever não é o mesmo que observar o seu próprio tráfego de API.

file.uploaded dispara quando um ficheiro é colocado na página Ficheiros, e file.deleted quando um é eliminado. Os seus dados são FileEventData: fileId, filename, mimeType, sizeBytes, direction, to, threadId, messageId, e uploadedAt ou deletedAt. to é o endereço a que o ficheiro pertence, ou null para um ficheiro que pertence a todo o espaço de trabalho.

Os eventos de ficheiros não estão no conjunto por omissão, por isso um endpoint só os recebe quando os nomeia em eventTypes. Um endpoint limitado a alguns endereços só é avisado sobre os ficheiros desses endereços, pelo que um ficheiro carregado para todo o espaço de trabalho, com to a null, não lhe é enviado.

form.submitted dispara quando alguém se inscreve através de um dos seus formulários, e form.confirmed quando uma inscrição pendente entra nas audiências, porque a pessoa abriu a ligação de confirmação ou porque a inscrição foi aprovada por si. form.submitted leva FormSubmittedEventData: formId, formName, submissionId, email, status, answers, audienceIds, sourceUrl e submittedAt. form.confirmed leva FormConfirmedEventData: formId, formName, submissionId, email, audienceIds, via, que é link ou approval, e confirmedAt.

Uma inscrição num formulário sem dupla confirmação envia form.submitted com status added e nenhum form.confirmed, por isso trate esse par como o momento em que alguém entra. Quem se inscreve de novo antes de confirmar mantém o mesmo submissionId, e form.submitted só volta a ser enviado quando as respostas mudaram. Os eventos de formulário não estão no conjunto por omissão, e um endpoint limitado a alguns endereços nunca os recebe, porque as inscrições pertencem a todo o espaço de trabalho.

Cada uma destas formas de dados é um TypedDict em openemail.types. Anote um evento verificado como WebhookPayload[FileEventData], por exemplo, e um verificador de tipos sabe o que event['data'] contém.

Provar 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'])

Um responseCode a None significa que não houve resposta nenhuma (DNS, TLS, um timeout), o que é um facto diferente de uma resposta que disse 0. Cada linha leva attempt e maxAttempts, por isso várias linhas podem descrever um só evento: o mesmo eventId entre elas é o evento, e o número da tentativa é a tentativa. nextAttemptAt diz quando está prevista a repetição automática que se segue a uma linha.

Enviar de novo

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'])

Uma entrega que continua a falhar é tentada até 8 vezes: no momento, depois ao fim de 1 minuto, 5 minutos, 30 minutos, 2 horas, 5 horas, 10 horas e 10 horas, cerca de 27 horas e meia no total. Só se repete uma falha que valha a pena repetir: sem resposta, 408, 425, 429 ou um 5xx. Um reenvio manda outra vez o evento guardado com o mesmo id, type, createdAt e data, pelo que um recetor que descarta ids já tratados o trata como o evento que já conhece. Só a assinatura é nova.

  • replay_delivery envia um evento agora e devolve o que o seu servidor respondeu. Funciona também numa tentativa entregue e nunca é repetido. Antes de enviar, as repetições automáticas desse evento que ainda não começaram ficam em pausa: continuam canceladas se o reenvio for entregue e retomam no seu horário se falhar.
  • Se nesse momento estiver a ser enviada uma repetição automática do mesmo evento, replay_delivery não envia nada e é recusado com 409 retry_in_progress, e enquanto outro reenvio dele ainda estiver a ser enviado é recusado com 409 replay_in_progress, para que quem recebe nunca receba duas cópias ao mesmo tempo, nem de dois reenvios feitos no mesmo instante. Espere alguns segundos e leia get_delivery, porque essa repetição ou esse reenvio pode entregá-lo. O reenvio é um evento de cada vez: nenhuma chamada reenvia todas as entregas falhadas.
  • Também recusa com 409 um endpoint desligado (webhook_disabled), um evento que o endpoint já não escuta (event_not_subscribed) ou já não cobre (event_out_of_scope), e uma tentativa sem evento guardado (delivery_not_replayable). get_delivery indica essa resposta de antemão como replayRefusal.

O SDK nunca repete replay_delivery por iniciativa própria, porque uma repetição após uma resposta perdida enviaria o evento outra vez.

Cada recusa lança OpenEmailApiError com status 409, is_conflict verdadeiro e o motivo como code, um dos valores de WEBHOOK_REPLAY_ERROR_CODES.

Parâmetros: webhooks.create

urlstrobrigatório
Para onde as entregas são feitas por POST. Apenas HTTPS, e o host não pode ser `localhost`, um nome `.localhost`/`.local`/`.internal`, nem um literal de IP de loopback, privado, CGNAT ou link-local. Isto é um fetch do lado do servidor para um endereço que você fornece, por isso esses são um 422 em `url`; a verificação lê o hostname tal como foi escrito e nunca resolve DNS. O que fica armazenado é a serialização, feita pelo parser de URL, daquilo que enviou, por isso `https://acme.com` lê-se de volta como `https://acme.com/`.
eventTypeslist[WebhookEvent]
Que eventos chegam a este endpoint: quaisquer dos nomes em `WEBHOOK_EVENTS`. `POST /webhooks` limita o array ao número de eventos que existem, por isso um a mais é um 422 em `eventTypes`; `PATCH` não o limita. Só o comprimento é limitado, e um nome repetido é armazenado e lido de volta exatamente como o enviou. Omitido ou vazio é armazenado como uma lista vazia, que é a razão de se ler de volta como `['*']`, e significa todos os eventos `email.*` menos `email.replied`, catorze hoje, e nunca as famílias de domínio, de supressão ou de ficheiros. Uma família acrescentada mais tarde nunca chega a um endpoint que não a nomeou, para que uma integração não possa começar a receber uma forma que nunca viu por causa de um lançamento.
descriptionstr
Uma etiqueta para o endpoint, com 200 caracteres no máximo, para que uma lista de webhooks se leia como nomes em vez de uma coluna de URLs. Omitida, é armazenada e devolvida como null.

Resposta: CreatedWebhookResource

objectLiteral['webhook']
Sempre `'webhook'`, o mesmo discriminador que uma leitura simples devolve, porque o segredo é uma chave a mais na forma habitual e não um tipo de objeto próprio. Se `secret` está presente decide-se pelo método que chamou, não por este campo.
idstr
O identificador do endpoint: `whe_` seguido de 24 caracteres hexadecimais. Todas as outras chamadas de webhook o recebem: `get`, `update`, `delete`, `rotate_secret`, `test`, `list_deliveries`, `list_all_deliveries`, `iterate_deliveries`, `get_delivery` e `replay_delivery`.
urlstr
O endpoint tal como ficou armazenado, depois de passar as verificações de HTTPS e de hosts bloqueados. É o URL analisado e novamente serializado, por isso compare com este valor e não com a string que enviou.
descriptionstr | None
A etiqueta que lhe deu, ou null se não deu nenhuma. Um `update` que envie um null explícito limpa-a de volta para null.
eventTypeslist[WebhookEvent] | ['*']
Os eventos subscritos, ou `['*']` quando o endpoint não nomeou nenhum. `['*']` é como uma lista vazia armazenada é apresentada na leitura e não pode ser enviado de volta, e representa os catorze eventos de mensagem e não o catálogo inteiro. `create` e `update` aceitam apenas os nomes literais dos eventos.
enabledbool
Se as entregas são tentadas; um endpoint desativado é saltado quando os eventos são despachados e mantém o seu segredo e o seu histórico de entregas. Aqui é sempre true, uma vez que `WebhookCreate` não tem `enabled` e só `WebhookPatch` tem.
lastDeliveryAtstr | None
Timestamp ISO 8601 da última TENTATIVA de entrega, e não do último sucesso. É carimbado também depois de um POST falhado, por isso diz-lhe que o endpoint foi tentado e `list_deliveries` diz-lhe como correu. Null até à primeira tentativa, e por isso sempre null no `create`.
createdAtstr
Timestamp ISO 8601 de quando o endpoint foi registado. `list` devolve os endpoints do mais recente para o mais antigo por este campo.
secretstr
A chave HMAC-SHA-256 que assina o `X-OpenEmail-Signature` de cada entrega: `whsec_` seguido de 32 bytes aleatórios em base64url, e o que entrega a `verify_webhook_signature`. Devolvida por `create` e `rotate_secret` e por mais nada. Uma leitura nunca a repete, por isso guarde-a já; um segredo perdido só pode ser substituído com `rotate_secret`, que invalida o antigo imediatamente.

Filtrar os registos

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 lê um endpoint e list_workspace_deliveries todos, ou os que endpoint_ids= nomeia, e ambos aceitam status=, since= e until=, os filtros do separador Entregas da consola. list_activity e list_workspace_activity leem o registo de auditoria: quem criou, alterou, ativou ou desativou, rodou, testou, reenviou ou removeu o quê. Cada um tem um list_all_… e um iterate_… ao lado, e cada linha do registo do espaço de trabalho traz endpointId.

since= e until= aceitam um datetime ou uma string ISO 8601. Um datetime sem fuso horário é lido como hora local e convertido para UTC, por isso passe um com fuso horário, como acima.

Referência