엔드포인트
`webhooks.list`, `list_all`, `iterate`, `get`, `create`, `update`, `delete`, `rotate_secret`, `test`, `get_delivery`, `replay_delivery`, 그리고 전송 로그와 활동 로그.
모든 메서드
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'])시크릿이 반환되는 것은 rotate_secret을 빼면 create 때뿐입니다. 읽기 호출은 절대 시크릿을 되돌려주지 않으므로, 다른 일을 하기 전에 먼저 저장하십시오. eventTypes를 생략하면 기본 집합, 즉 email.replied를 제외한 모든 email.* 이벤트를 받습니다. email.replied, domain.*, suppression.*, file.*, form.*는 엔드포인트가 이를 명시할 때만 전달됩니다.
rotate_secret에는 겹침 구간이 없습니다. 예전 시크릿은 즉시 동작을 멈추므로, 회전하기 전에 새 시크릿을 먼저 배포하십시오. 이 호출은 절대 자동으로 재시도되지 않습니다. 재시도하면 두 번째 회전이 일어나 첫 번째 시도가 돌려준 시크릿이 무효가 되기 때문입니다.
구독할 수 있는 이벤트
WEBHOOK_EVENTS는 목록을 렌더링할 수 있도록 내보내집니다. 이 이벤트는 이 API의 이벤트가 아니라 **메일함**의 이벤트입니다. email.received는 앱에 메일이 도착할 때 발생하고, email.sent는 작성기가 메시지를 보냈을 때 발생합니다. 구독하는 것은 자신의 API 트래픽을 지켜보는 것과 다릅니다.
file.uploaded는 파일이 파일 페이지에 올라갈 때, file.deleted는 파일이 삭제될 때 발생합니다. 데이터는 FileEventData이며 fileId, filename, mimeType, sizeBytes, direction, to, threadId, messageId, 그리고 uploadedAt 또는 deletedAt을 담습니다. to는 파일이 속한 주소이고, 워크스페이스 전체에 속한 파일이면 null입니다.
파일 이벤트는 기본 집합에 없으므로, 엔드포인트가 eventTypes에 명시할 때만 받습니다. 일부 주소로 제한된 엔드포인트는 그 주소의 파일에 관한 이벤트만 받으므로, to가 null인 워크스페이스 전체용 업로드는 전달되지 않습니다.
form.submitted는 누군가 내 양식 중 하나로 가입할 때 발생하고, form.confirmed는 그 사람이 확인 링크를 열었거나 내가 승인해서 확인 대기 중인 가입이 오디언스에 들어갈 때 발생합니다. form.submitted는 FormSubmittedEventData를 담으며 formId, formName, submissionId, email, status, answers, audienceIds, sourceUrl, submittedAt이 들어 있습니다. form.confirmed는 FormConfirmedEventData를 담으며 formId, formName, submissionId, email, audienceIds, link 또는 approval인 via, 그리고 confirmedAt이 들어 있습니다.
더블 옵트인이 없는 양식에서의 가입은 status가 added인 form.submitted를 보내고 form.confirmed는 보내지 않으므로, 그 조합을 누군가 들어온 순간으로 취급하십시오. 확인 전에 다시 가입한 사람은 같은 submissionId를 유지하며, form.submitted는 응답이 바뀐 경우에만 다시 전송됩니다. 양식 이벤트는 기본 집합에 없으며, 가입은 워크스페이스 전체에 속하므로 일부 주소로 제한된 엔드포인트는 이 이벤트를 받지 않습니다.
이 데이터 형태는 각각 openemail.types의 TypedDict입니다. 예를 들어 검증된 이벤트에 WebhookPayload[FileEventData]라고 타입을 표기하면, 타입 검사기는 event['data']에 무엇이 담기는지 압니다.
동작 확인하기
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'])responseCode가 None이면 응답이 아예 없었다는 뜻이며(DNS, TLS, 타임아웃), 이는 응답이 0이라고 말한 것과는 다른 사실입니다. 각 행에는 attempt와 maxAttempts가 담기므로 여러 행이 하나의 이벤트를 설명할 수 있습니다. 행들에 걸쳐 동일한 eventId가 그 이벤트이고, 시도 번호가 각각의 시도입니다. nextAttemptAt은 그 행 다음의 자동 재시도가 언제 예정되어 있는지 알려 줍니다.
다시 보내기
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'])계속 실패하는 전달은 최대 8번 시도합니다. 즉시, 그리고 1분, 5분, 30분, 2시간, 5시간, 10시간, 다시 10시간 뒤이며, 모두 합쳐 약 27시간 반입니다. 반복할 가치가 있는 실패만 반복합니다. 응답 없음, 408, 425, 429, 5xx가 여기에 해당합니다. 재전송은 저장된 이벤트를 같은 id, type, createdAt, data로 다시 보내므로, 이미 처리한 id를 버리는 수신 측은 이를 이미 아는 이벤트로 취급합니다. 새로 바뀌는 것은 서명뿐입니다.
replay_delivery는 이벤트 하나를 지금 보내고 서버가 응답한 내용을 반환합니다. 전달에 성공한 시도에도 쓸 수 있고, 절대 재시도되지 않습니다. 보내기 전에 그 이벤트의 자동 재시도 가운데 아직 시작되지 않은 것은 일시 중지됩니다. 재전송이 전달되면 계속 취소된 상태로 남고, 실패하면 예정대로 다시 진행됩니다.- 그 순간 같은 이벤트의 자동 재시도가 전송 중이면
replay_delivery는 아무것도 보내지 않고 409retry_in_progress로 거절되고, 같은 이벤트의 다른 재전송이 아직 전송 중이면 409replay_in_progress로 거절되므로, 같은 순간에 보낸 두 재전송이라도 수신 측이 동시에 두 개를 받는 일은 없습니다. 몇 초 기다린 뒤get_delivery를 확인하세요. 그 재시도나 재전송으로 전달될 수 있습니다. 재전송은 한 번에 이벤트 하나이며, 실패한 전달을 모두 다시 보내는 호출은 없습니다. - 또한 꺼진 엔드포인트(
webhook_disabled), 엔드포인트가 더 이상 구독하지 않는 이벤트(event_not_subscribed)나 더 이상 포함하지 않는 이벤트(event_out_of_scope), 저장된 이벤트가 없는 시도(delivery_not_replayable)도 409로 거절합니다.get_delivery는 그 결과를replayRefusal로 미리 알려 줍니다.
SDK는 replay_delivery를 스스로 재시도하지 않습니다. 응답을 잃은 뒤 재시도하면 이벤트가 한 번 더 전송되기 때문입니다.
거부는 모두 status가 409이고 is_conflict가 true이며 사유를 code에 담은 OpenEmailApiError를 발생시킵니다. 그 값은 WEBHOOK_REPLAY_ERROR_CODES에 있는 값 중 하나입니다.
파라미터: webhooks.create
urlstr필수- 전달 요청이 POST될 주소입니다. HTTPS만 허용되며, 호스트는 `localhost`, `.localhost`/`.local`/`.internal` 이름, 루프백·사설·CGNAT·링크 로컬 IP 리터럴일 수 없습니다. 여러분이 지정한 주소로 서버가 직접 요청을 보내는 것이므로 그런 값은 `url`에 대한 422이며, 이 검사는 적힌 그대로의 호스트 이름만 읽고 DNS는 조회하지 않습니다. 저장되는 값은 보낸 것을 URL 파서가 다시 직렬화한 형태이므로, `https://acme.com`은 `https://acme.com/`으로 읽힙니다.
eventTypeslist[WebhookEvent]- 이 엔드포인트로 전달할 이벤트이며, `WEBHOOK_EVENTS`에 있는 이름 중 아무거나 지정할 수 있습니다. `POST /webhooks`는 배열 길이를 존재하는 이벤트 수만큼으로 제한하므로 그보다 하나라도 많으면 `eventTypes`에 대한 422이며, `PATCH`는 제한하지 않습니다. 제한되는 것은 길이뿐이고, 반복된 이름은 보낸 그대로 저장되어 그대로 읽힙니다. 생략하거나 비워 두면 빈 목록으로 저장되며, 그래서 읽을 때 `['*']`로 나타납니다. 이는 `email.replied`를 제외한 모든 `email.*` 이벤트, 오늘 기준 열네 개를 뜻하며 도메인, 수신 거부, 파일 계열은 결코 포함하지 않습니다. 나중에 추가되는 계열은 그 이름을 지정하지 않은 엔드포인트에 절대 도달하지 않으므로, 릴리스 때문에 통합이 한 번도 본 적 없는 형태를 받기 시작하는 일은 없습니다.
descriptionstr- 엔드포인트에 붙이는 라벨이며 최대 200자입니다. 덕분에 웹훅 목록이 URL만 늘어선 열이 아니라 이름으로 읽힙니다. 생략하면 null로 저장되고 null로 반환됩니다.
응답: CreatedWebhookResource
objectLiteral['webhook']- 항상 `'webhook'`이며, 일반 읽기가 반환하는 것과 같은 판별자입니다. 시크릿은 별도의 객체 타입이 아니라 평범한 형태에 키 하나가 더해진 것이기 때문입니다. `secret`이 포함되는지는 이 필드가 아니라 어떤 메서드를 호출했는지로 결정됩니다.
idstr- 엔드포인트의 식별자로, `whe_` 뒤에 16진수 24자가 붙습니다. `get`, `update`, `delete`, `rotate_secret`, `test`, `list_deliveries`, `list_all_deliveries`, `iterate_deliveries`, `get_delivery`, `replay_delivery` 등 다른 모든 웹훅 호출이 이 값을 받습니다.
urlstr- HTTPS 검사와 차단 호스트 검사를 통과해 저장된 엔드포인트 주소입니다. 파싱된 URL을 다시 직렬화한 값이므로, 보낸 문자열이 아니라 이 값과 비교하십시오.
descriptionstr | None- 지정한 라벨이며, 지정하지 않았으면 null입니다. `update`로 명시적 null을 보내면 다시 null로 비워집니다.
eventTypeslist[WebhookEvent] | ['*']- 구독한 이벤트이며, 엔드포인트가 아무것도 지정하지 않았으면 `['*']`입니다. `['*']`는 비어 있는 저장 목록이 읽을 때 표현되는 방식일 뿐 되돌려 보낼 수는 없으며, 전체 카탈로그가 아니라 열네 개의 메시지 이벤트를 뜻합니다. `create`와 `update`는 리터럴 이벤트 이름만 받습니다.
enabledbool- 전달을 시도할지 여부입니다. 비활성화된 엔드포인트는 이벤트를 발송할 때 건너뛰지만 시크릿과 전달 이력은 그대로 유지합니다. `WebhookCreate`에는 `enabled`가 없고 `WebhookPatch`에만 있으므로, 여기서는 항상 true입니다.
lastDeliveryAtstr | None- 마지막 성공이 아니라 마지막 전달 시도의 ISO 8601 타임스탬프입니다. 실패한 POST 뒤에도 찍히므로, 이 값은 엔드포인트를 시도했다는 사실을 알려 주고 결과가 어땠는지는 `list_deliveries`가 알려 줍니다. 첫 시도 전까지는 null이므로 `create` 직후에는 언제나 null입니다.
createdAtstr- 엔드포인트가 등록된 시각의 ISO 8601 타임스탬프입니다. `list`는 이 필드를 기준으로 최신 엔드포인트부터 반환합니다.
secretstr- 각 전달의 `X-OpenEmail-Signature`에 서명하는 HMAC-SHA-256 키입니다. `whsec_` 뒤에 무작위 32바이트를 base64url로 인코딩한 값이 붙으며, `verify_webhook_signature`에 넘기는 값입니다. `create`와 `rotate_secret`만 반환하고 그 밖에는 어디서도 반환하지 않습니다. 읽기 호출은 절대 되돌려주지 않으므로 지금 저장하십시오. 잃어버린 시크릿은 `rotate_secret`으로만 교체할 수 있으며, 그러면 예전 시크릿은 즉시 무효가 됩니다.
로그 거르기
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는 엔드포인트 하나를, list_workspace_deliveries는 모든 엔드포인트나 endpoint_ids=로 지정한 것을 읽으며, 둘 다 콘솔 전송 탭의 필터인 status=, since=, until=을 받습니다. list_activity와 list_workspace_activity는 감사 로그를 읽습니다: 누가 무엇을 만들고, 바꾸고, 끄거나 켜고, 교체하고, 테스트하고, 재전송하고, 삭제했는지. 각각 옆에 list_all_…과 iterate_…가 있고, 워크스페이스 로그의 모든 행에는 endpointId가 붙습니다.
since=와 until=은 datetime이나 ISO 8601 문자열을 받습니다. naive datetime은 현지 시각으로 해석되어 UTC로 변환되므로, 위 예제처럼 aware 값을 넘기십시오.
레퍼런스
webhooks.list()전체 레퍼런스webhooks.list_all()전체 레퍼런스webhooks.iterate()전체 레퍼런스webhooks.get()전체 레퍼런스webhooks.create()전체 레퍼런스webhooks.update()전체 레퍼런스webhooks.delete()전체 레퍼런스webhooks.rotate_secret()전체 레퍼런스webhooks.test()전체 레퍼런스webhooks.list_deliveries()전체 레퍼런스webhooks.list_all_deliveries()전체 레퍼런스webhooks.iterate_deliveries()전체 레퍼런스webhooks.get_delivery()전체 레퍼런스webhooks.replay_delivery()전체 레퍼런스webhooks.list_workspace_deliveries()전체 레퍼런스webhooks.list_all_workspace_deliveries()전체 레퍼런스webhooks.iterate_workspace_deliveries()전체 레퍼런스webhooks.list_activity()전체 레퍼런스webhooks.list_all_activity()전체 레퍼런스webhooks.iterate_activity()전체 레퍼런스webhooks.list_workspace_activity()전체 레퍼런스webhooks.list_all_workspace_activity()전체 레퍼런스webhooks.iterate_workspace_activity()전체 레퍼런스