エンドポイント
`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 には移行期間がない。古いシークレットは即座に無効になるため、ローテーションの前に新しいシークレットをデプロイしておくこと。この呼び出しが自動でリトライされることはない。リトライすれば 2 回目のローテーションが起き、1 回目の試行で返されたシークレットが無効になるからである。
購読できるイベント
一覧を表示できるよう 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 を持つため、複数の行が 1 つのイベントを表すことがある。行をまたいで同一の 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は 1 件のイベントを今すぐ送り、サーバーの応答を返す。配信済みの試行にも使え、自動でリトライされることはない。送信の前に、そのイベントの自動再試行のうちまだ始まっていないものは一時停止される。再送が配信されればそれらはキャンセルされたままになり、失敗すれば予定どおり再開する。- その瞬間に同じイベントの自動再試行が送信中であれば、
replay_deliveryは何も送らず 409retry_in_progressで拒否され、同じイベントの別の再送がまだ送信中であれば 409replay_in_progressで拒否されるため、同じ瞬間に 2 回再送しても、受信側が同時に 2 通を受け取ることはない。数秒待ってからget_deliveryを読むこと。その再試行や再送で配信される場合がある。再送は 1 件ずつであり、失敗した配信をすべて再送する呼び出しはない。 - さらに、無効化されたエンドポイント(
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 リテラルは指定できない。これは利用者が指定したアドレスに対するサーバー側からの fetch であるため、これらは `url` に対する 422 になる。検査はホスト名を書かれたとおりに読むだけで、DNS の解決は行わない。保存されるのは、送られた値を URL パーサーがシリアライズしたものである。そのため `https://acme.com` は `https://acme.com/` として読み出される。
eventTypeslist[WebhookEvent]- このエンドポイントに届くイベント。`WEBHOOK_EVENTS` に含まれる名前を指定する。`POST /webhooks` は配列の長さを、存在するイベント数を上限として制限するため、それを 1 つ超えると `eventTypes` に対する 422 になる。`PATCH` には上限がない。制限されるのは長さだけであり、同じ名前が重複していても、送ったとおりに保存され読み出される。省略または空の場合は空のリストとして保存され、読み出すと `['*']` になるのはそのためである。これは `email.replied` を除くすべての `email.*` イベント、現時点で 14 種類を意味し、ドメイン系、抑制系、ファイル系のイベント群は決して含まない。後から追加されたイベント群が、それを指定していないエンドポイントに届くことはない。そのため、リリースを理由に、インテグレーションが見たことのない形のデータを受け取り始めることはない。
descriptionstr- エンドポイントのラベル。最大 200 文字。Webhook の一覧が URL の列ではなく名前として読めるようにするためのもの。省略した場合は null として保存され、null として返る。
レスポンス: CreatedWebhookResource
objectLiteral['webhook']- 常に `'webhook'`。通常の読み取りが返すのと同じ判別子である。シークレットは独自のオブジェクト型ではなく、通常の形に 1 つキーが増えただけだからである。`secret` が含まれるかどうかは、このフィールドではなく、どのメソッドを呼んだかによって決まる。
idstr- エンドポイントの識別子。`whe_` に続けて 16 進の 24 文字が並ぶ。他のすべての Webhook 呼び出し、すなわち `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] | ['*']- 購読しているイベント。エンドポイントが何も指定しなかった場合は `['*']`。`['*']` は、保存された空のリストが読み取り時に表現された形であり、送り返すことはできない。これはカタログ全体ではなく、14 種類のメッセージイベントを表す。`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 は 1 つのエンドポイントを、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()完全なリファレンス