コンテンツにスキップ

Webhook のベストプラクティス

すばやく応答し、処理はあとで行う

Section titled “すばやく応答し、処理はあとで行う”

署名を検証し、イベントをキューに入れて(データベースの行、キュー、バックグラウンドジョブ)、すぐに 2xx を返します。SpacePost が待つのは最大 15 秒で、遅いハンドラーは再試行と重複につながります。

配信は少なくとも 1 回です。処理した配信の webhook-id を保存し(一意制約で十分です)、重複はスキップしてください。

const inserted = await db.insert(processedWebhooks).values({ id: webhookId }).onConflictDoNothing().returning();
if (inserted.length === 0) return new Response("duplicate", { status: 200 });

生のボディを使い、5 分より古いタイムスタンプは拒否し、署名は定数時間で比較します(SDK とライブラリが行います)。署名の検証をご覧ください。

イベントは、特に再試行のあとに、順序が入れ替わって届くことがあります。ペイロードの created_at を保存済みの値と比較し、状態が前に進む変更だけを適用してください。SpacePost 自身の last_event も同じルールに従っています。

イベント 典型的な対応
email.bounced データベースでそのアドレスを無効としてマークし、送信をやめます。
email.complained 不可欠ではないメールの配信を、そのユーザーについてすぐに停止します。
email.delivery_delayed 通常は何もしません。パスワードのリセットのように時間が重要なメールの場合は、アラートを出します。
email.failed data.failed.reason をログに記録してアラートを出し、必要に応じて修正のうえ再試行します。
email.suppressed 宛先が送信停止リストに載っていました。管理画面に表示します。

処理するイベントだけを購読してください。イベントが少ないほど、通信量も失敗も減ります。

  • 有効な証明書を使って HTTPS で提供します。
  • シークレットや、個人情報を含むペイロード全体をログに残さないでください。
  • 他の動作を持たない専用のパスを使い、汎用的なボディを返します。
  • signing_secret は、ソース管理ではなくシークレットマネージャーに保管します。

ダッシュボードの配信ログを確認するか、繰り返される失敗にアラートを設定してください。エンドポイントが約 27 時間以上ダウンすると、すべての再試行を使い切ります。その後は手動で再送する必要があります。