をテストするには SendGrid Event Webhook ローカルホスト上でハンドラーをローカルで実行し、そのポートを次のように公開します。 npx portpreview PORT、結果を入力します HTTPS エンドポイントとして SendGridの投稿 URL を使用してすべてのリクエストを検証します。 Signed Event Webhook イベントを処理する前に公開キー。
なんと SendGrid Event Webhook 送信します
の Event Webhook その後何が起こったかを報告する SendGrid メッセージを受け入れます。配信可能性イベントには以下が含まれます processed, delivered, deferred, bounce、 そして dropped。エンゲージメント イベントには以下が含まれます open, click、スパム報告、およびサブスクリプションの変更。正確なフィールドはイベント タイプによって異なるため、主に次のようにルーティングします。 event オプションのフィールドをオプションとして扱います。
リクエストボディは JSON 配列、必ずしも 1 つのオブジェクトである必要はありません。 SendGrid 複数のイベントを 1 つのイベントに含めることができます POST。想定するハンドラー req.body.event 黙ってバッチを見逃してしまいます。役人 Event Webhook 参照 イベント名とフィールドを文書化します。 sg_event_id そして sg_message_id.
イベントをコマンドではなく事実として使用します。たとえば、 delivered イベントはメッセージのステータスを更新できますが、 click エンゲージメントレコードを追加できます。を作ることは避けてください。 click ハンドラーは、単にリクエストが順番どおりに到着しなかったために、後の購読解除状態を上書きします。
1. ローカルエンドポイントを作成する
これ Express この例では、意図的に raw-body パーサーを SendGrid ルート。署名の検証は正確なバイト数に依存します SendGrid 署名済み。解析と再シリアライズ JSON それらのバイトを変更できます。
import express from 'express';
import { EventWebhook, EventWebhookHeader } from '@sendgrid/eventwebhook';
const app = express();
const verifier = new EventWebhook();
const publicKey = verifier.convertPublicKeyToECDSA(
process.env.SENDGRID_WEBHOOK_PUBLIC_KEY,
);
app.post(
'/webhooks/sendgrid',
express.raw({ type: 'application/json' }),
async (req, res) => {
const signature = req.get(EventWebhookHeader.SIGNATURE());
const timestamp = req.get(EventWebhookHeader.TIMESTAMP());
if (!signature || !timestamp || !verifier.verifySignature(
publicKey,
req.body,
signature,
timestamp,
)) {
return res.status(403).send('invalid signature');
}
let events;
try {
events = JSON.parse(req.body.toString('utf8'));
} catch {
return res.status(400).send('invalid JSON');
}
if (!Array.isArray(events)) {
return res.status(400).send('expected an event array');
}
await enqueueNewEvents(events);
return res.sendStatus(204);
},
);
app.use(express.json());
app.listen(3000);
公式ヘルパーをインストールするには npm install @sendgrid/eventwebhook。マウントグローバル express.json() このルートの後に配置するか、このパスを明示的に除外します。同じルールが次の場合にも適用されます Next.js, Fastify, NestJS、サーバーレス機能、および API ゲートウェイ: 検証が成功するまで、元の本文を文字列またはバイト バッファーとして保持します。役人 SendGrid ノード リポジトリに一致するものがある 署名済み Event Webhook 例.
2.与える SendGrid の HTTPS URL
アプリケーションを実行し続けてから、 open 2 番目の端末:
npx portpreview 3000
PortPreview 公開資料を印刷します HTTPS 起源。もしそうなら https://example.portpreview.dev、完全な投稿 URL は次のとおりです。
https://example.portpreview.dev/webhooks/sendgrid
パスはルートと正確に一致する必要があります。テスト中はトンネル プロセスを維持します。トンネルはトラフィックを転送します。ローカル サーバーに代わるものではないため、接続の失敗は通常、アプリが停止しているか、別のポートでリッスンしているか、トンネルが到達できない方法でバインドされていることを意味します。
3. を設定します。 Event Webhook で SendGrid
- で SendGrid UI、 open 設定 > メール設定.
- Webhook 設定で、 open Event Webhooks そして選択してください 新しい Webhook を作成する.
- それを有効にして、 PortPreview URL を投稿 URL として指定し、アプリケーションに必要なアクションのみを選択します。
- [セキュリティ機能] で、有効にします。 Signed Event Webhook.
- Webhook を保存して、再度open その設定を変更し、生成された公開検証キーをコピーして、次のように保存します。
SENDGRID_WEBHOOK_PUBLIC_KEY. - 使用 Test Your Integration、その後、実際のメッセージを送信して、重要なイベント タイプを実行します。
SendGridさんの現在 セットアップガイド このテストでは、実際のメール送信からのデータではなく、サンプル イベントが送信されることに注意してください。テスト前に保存する signature 検証: キーペアは、 Signed Event Webhook 設定が保存されます。
どうやって SendGridの署名済み Webhook 検証が機能する
Signed Event Webhook 用途 ECDSA. SendGrid は秘密キーを保持し、対応する公開検証キーを表示します。各配送には以下が含まれます X-Twilio-Email-Event-Webhook-Signature そして X-Twilio-Email-Event-Webhook-Timestamp。検証の対象となるのは、 timestamp 生のペイロードバイトと連結された SHA-256 ハッシュ;の signature は Base64-エンコードされた。公式ヘルパーは公開鍵の変換を処理します。 signature デコード、ハッシュ、および ECDSA 検証。
これは非対称検証です。表示される値は HMAC シークレットではなく公開キーです。ペイロードを実行しないでください JSON.stringify()、空白のトリミング、改行の追加、または一度に 1 つの配列要素を検証します。まず完全なリクエスト バイトを確認してから、配列を解析します。見る SendGridさんの セキュリティ機能のドキュメント アルゴリズムとヘッダーについて。
有効な signature 署名されたバイトが所有者からのものであることを証明します。 SendGridの秘密キーは変更されていませんでした。イベント処理を冪等にしたり、任意のアクションを許可したり、イベントが新しいことを証明したりするものではありません。これらは別個のコントロールです。
バッチ処理を冪等にする
SendGrid 再試行が失敗しました POSTと、ネットワークが正常な応答を失う可能性があります。したがって、重複配信は正常です。各イベントを利用する sg_event_id プライマリ重複排除キーとして、一意のデータベース制約を使用します。製品が複数を組み合わせている場合 SendGrid アカウントまたは環境、名前空間はプロバイダーおよびアカウントまたは環境ごとのキーです。
async function enqueueNewEvents(events) {
for (const event of events) {
await db.transaction(async (tx) => {
const inserted = await tx.webhookReceipts.insertIfAbsent({
provider: 'sendgrid',
eventId: event.sg_event_id,
receivedAt: new Date(),
});
if (!inserted) return;
await tx.jobs.enqueue({
type: 'process-sendgrid-event',
payload: event,
});
});
}
}
レシートの挿入と永続的なエンキューは一緒にコミットする必要があります。バッチが永続的に保存された後のみ 2xx を返します。 accepted。他のイベントがコミットされた後に 1 つのイベントが失敗した場合、2xx 以外の応答によってリクエスト全体が返される可能性があります。重複排除により、次回の試行ではすでにイベントをスキップできます accepted そして安全に続行してください。インメモリを使用しないでください Set 本番環境では再起動によって消去され、複数のインスタンスで共有されないためです。より広範な Webhook の再試行とべき等性ガイド 耐久性のあるパターンをカバーします。
ステータス コードを選択する前に再試行を理解する
によると SendGridさんの Event Webhook ドキュメントによれば、2xx 応答は POST 成功。 2xx 以外の応答では、イベント後最大 24 時間、間隔をあけて再試行が行われます。これは、新しい失敗イベントごとのローリング ウィンドウです。その行動は永久的なものを意味します signature 失敗すると試行が繰り返される可能性もありますが、保存しなかったイベントに対して 2xx を返すとイベントが失われます。
- 2xx: 完全なバッチは認証されており、永続的に保存されています。 accepted、またはすべてのイベントはすでに知られています。
- 4xx: 不正な入力または認証されていない入力。安全な診断のみをログに記録します。期待する SendGridの一般的な非 2xx 再試行動作。
- 5xx: 再試行する必要がある一時的なデータベース、キュー、またはアプリケーションの障害。
リクエスト パスは短くしてください。外側の形状を確認して検証し、アトミックに重複を排除してキューに入れてから、応答します。電子メール分析の更新を実行し、 CRM ワーカーでの同期と通知。
ローカルのトラブルシューティング SendGrid Webhook
の signature 常に無効です
最も一般的な原因は次のとおりです。 JSON 検証前にミドルウェアが本体を消費します。検証者が原本を受け取ったことを確認する Buffer、先頭または末尾の空白も含みます。次に、公開キーがまさにこのものに属していることを確認します Event Webhook 構成とその両方 Twilio ヘッダーは変更されずにアプリに到達します。環境を変更した後、ローカル プロセスを再起動します。
テスト統合は成功しますが、実際のイベントが表示されません
Webhook が有効になっていること、および必要なアクションが選択されていることを確認します。開くには必要です open 追跡、そして clickが必要です click トラッキング。また、テスト リクエストには例が含まれていることにも注意してください。実際の送信を使用して、運用環境に似たフィールドとシーケンスを検証します。
エンドポイントは 404 または 502 を返します
404 の場合、構成されたパスを次と比較します。 /webhooks/sendgrid。ゲートウェイ エラーの場合は、ローカル アプリがゲートウェイに渡されたのと同じポートで実行されていることを確認してください。 PortPreview。リクエストが到着しても 500 が返された場合は、ローカル ログを検査し、ハンドラーを一時的に検証と永続的なキャプチャに減らします。
イベントが重複しているか順番が間違っている
これは配信システムの現実であり、トンネルがトラフィックを複製したという証拠ではありません。重複排除の基準 sg_event_id、可能であれば状態遷移を単調にし、イベント時間を受信時間とは別に保存します。を使用します。 ローカル Webhook デバッグ ワークフロー トランスポート、認証、ビジネス ロジックの障害を分離します。
ローカルおよび運用環境での使用のためのセキュリティ チェックリスト
- 使用 HTTPS そしてすべてを検証します signature イベントの詳細を解析または記録する前に。
- Webhook キーが変更されたときに公開検証キーを適切に更新できるように、構成内に公開検証キーを保持します。
- 受け入れる POST のみ、リクエスト サイズを制限し、解析された値が配列であることを検証し、処理するイベント名のみを許可します。
- PII を配置しないでください SendGrid カテゴリまたは固有の引数。 SendGridのリファレンスでは、これらのフィールドは保存され、PII として扱われないことを明示的に警告しています。
- 管理セッション、デバッグ コンソール、または無関係なローカル ルートを同じ一時オリジン経由で公開しないでください。
- 受信者のアドレス、ペイロード、 signature必要かつ適切に編集されていない限り、s、または環境値。
- 一時的なトンネル URL を安定した運用環境に置き換えます。 HTTPS テスト後にエンドポイントを削除し、古い Webhook 構成を無効にします。
SendGrid も使用できます OAuth 2.0 のために Event Webhook 単独または並行してのセキュリティ signatures.デプロイメントにベアラーが必要な場合は、token ライフサイクル制御では、独自のセキュリティ ガイドを作成するのではなく、公式のセキュリティ ガイドに従ってください。 token 交換。署名検証は、正確な情報を結び付けるため、依然として価値があります。 timestamp そしてペイロードバイト。
本番環境に対応した受け入れテスト
- 署名されたテスト要求を送信し、2xx 応答を確認します。
- ペイロード バイトを 1 つ変更し、データベースへの書き込みがない 403 を確認します。
- 同一の有効なリクエストを再生し、ジョブやビジネス アクションが重複していないことを確認します。
- を送信します JSON 配列の代わりにオブジェクトを使用し、制御された 400 を確認します。
- データベースを一時的に停止し、5xx を確認して復元し、再試行されることを確認します。 accepted 一度。
- 実際のメールを送信し、選択した配信イベントとエンゲージメント イベントが同じパスをたどることを確認します。
これらのチェックに合格したら、検証と冪等性のロジックを変更せずにエンドポイントを運用環境に移動します。より詳細な暗号障害モードについては、「 ウェブフック signature 検証ガイド.
