← SaaS Workflowsの一覧へ
SaaSDubWebhooksNode.js

Dub webhook署名検証:raw bodyと再送の境界をNode.jsで確認する

5 分で読める

このトピックを深掘りする

SaaSの導入・連携

AIに仕事の続きを渡すために、会議の原文、未完了の宿題、確認できない項目をどう残すか。導入判断と連携実装を掘り下げる。

全6本中 3 本目。

このトピックの記事一覧

シリーズ全体の流れを見ながら、次に読む記事へ進めます。 初めての方はホームへ →

Dubの登録通知を次の作業へ渡すなら、受信した本文をそのまま信用せず、署名を確かめてから必要な項目を取り出す。この記事では、Dub-Signatureの検証と、lead.createdからAI向けの作業記録を作る境界を、通信しない実行例で確認する。

確認日は2026年10月22日。Dubの実配信、外部DBへの保存、AIへの送信は未検証。Dubへの紹介リンクは使っておらず、公式資料へ案内する。登録・購入をDubへ送る側はconversion trackingの記事、URLを作る側はUTMの記事で扱う。

最初に決めるのは、通知の後に残す情報

たとえば登録の通知から「顧客の初期設定を確認する」という次の仕事へ進める場合、通知全文をAIへ渡す必要はない。今回の例はイベントID、種類、作成日時、イベント名、自社顧客IDだけを取り出す。メールアドレスは出力しない。顧客IDも個人に結びつく場合があるため、実運用では送信先のデータ利用条件を確かめる。

この記録は登録イベントを受信した根拠であり、支払完了や報酬確定の証拠にはしない。AIには、元イベントID、確認済みの項目、次に人が確認する作業を分けて渡す。サンプルは記録を標準出力へ出すところまでで、作業の作成や通知は行わない。

Dubの設定と署名の仕様

当日取得した公式の導入ガイドでは、WebhookはBusiness以上で利用する。Webhooks設定で受信URLとイベントを選び、Configurationで署名用secretを確認する。ここでは作成操作を実施していない。secretはサーバー側の環境変数などで管理し、ブラウザや記事、Gitへ含めない。

公式の署名検証ガイドは、受信したraw bodyをsecretでHMAC-SHA256へ通し、hex形式のDub-Signatureと比較する手順を示している。req.json()で解析してからJSON.stringify()で戻した文字列は、空白や改行が変わり得る。検証にはawait req.text()で取得した元の本文を使う。

今回のNode.js/Bun例は64桁のhex形式を確認し、同じ長さのBufferをtimingSafeEqualで比較する。これはローカル例の実装方法であり、公式SDKの使用例ではない。

const rawBody = await req.text()
const signature = req.headers.get('Dub-Signature')
// receiveLeadは下の配布ファイルからimportする。
const record = receiveLead(rawBody, signature, process.env.DUB_WEBHOOK_SECRET)

上の断片だけではHTTPハンドラーは完成しない。実運用ではbodyサイズ制限、例外のHTTP応答への変換、耐久性のある保存を追加する。保存前に成功応答を返すと、その後の処理失敗を回収できない場合がある。

資格情報なしでraw bodyの違いを再現する

dub-webhook-demo.mjsを空のフォルダへ保存して実行する。BunまたはNode.js 22以降を使う。公開された合成鍵だけで署名を作り、通信・資格情報・ファイル保存は使わない。

bun dub-webhook-demo.mjs
# または
node dub-webhook-demo.mjs

同じ合成JSONで実行した結果は次のとおり。BunとNodeの出力は一致した。

入力 結果
署名した元の改行付き本文 eventIdがevt_demoの作業記録を出力
本文の末尾へ空白を追加 tamperedAccepted: false
JSONを解析して改行なしで再生成 reserializedAccepted: false
元の本文と署名をもう一度渡す replayAccepted: true

署名が正しくても、JSONとして壊れた本文や、対象外イベント、顧客IDの欠落は受理しない。lead.createdの公式資料では、トップレベルにid・event・createdAt・dataがあり、顧客はdata.customerに入る。今回の例は自社顧客IDのあるleadだけを対象にする。公式の全フィールドを検証するスキーマではない。

署名確認と再送の制御は別に実装する

表のreplayAccepted: trueが、この例の限界を示している。同じ正当な本文と署名なら再び検証を通る。この例には有効期限や永続的な重複排除がない。

公式導入ガイドでは、2XX以外の応答で再送し、指数バックオフの遅延は5回目以降24時間が上限になる。また20回連続の失敗でWebhookを無効化すると説明している。「必ず無期限に届く」とは扱わず、Event Logsで失敗と応答を確認する。

本番へ進めるなら、イベントIDを一意キーにした永続的な受信記録と、後続作業の状態を残す設計を検討する。署名確認・入力検証・保存の完了後に成功応答を返し、後続処理の失敗を再開できるようにする。ここではDB、キュー、同時受信、再送順序、Dubの実配信を検証していない。

使う前に確認する範囲

ローカルのテストでは、正しい署名、改変、再整形、署名欠落・形式不正・別の鍵、署名済み不正JSON、null、対象外イベント、必須値欠落、同じ入力の再受理を確かめた。合成試験から、実サービスの配送保証や業務の時間短縮は推定しない。

別の受信例はCircleback Webhookの記事、全体の実行例はSaaS Workflowsと最初のローカルデモから参照できる。両製品の署名ヘッダーやpayloadを混同せず、対象製品の仕様で検証する。

よくある質問

Q. Dub webhookの署名はどのヘッダーで確認しますか?
公式ガイドではDub-Signatureです。受信したraw bodyと署名用secretからHMAC-SHA256を計算し、hex形式のヘッダーと比較します。実Dub配信は今回未検証です。
Q. req.json()の後に署名を検証できますか?
JSONを再生成すると空白や改行が変わり、署名が一致しない場合があります。先にreq.text()で元の本文を取得して検証し、その後でJSONを解析します。合成例で再整形による不一致を確認しました。
Q. Dub webhookに必要なプランは?
2026年10月22日取得の公式導入ガイドはBusiness以上としています。Webhooks設定で受信URLとイベントを選び、Configurationで署名用secretを確認します。今回作成操作はしていません。
Q. 署名が正しければ重複通知を防げますか?
この例は同じ本文と署名を再び受理します。有効期限や永続的な重複排除はありません。本番ではイベントIDと後続作業の状態を保存する設計を検討してください。DBや再送制御は未検証です。
Q. サンプルはAPIキーやAIサービスを使いますか?
使いません。公開された合成鍵で署名を作り、必要な項目だけを標準出力へ出します。通信・資格情報・ファイル保存・AIへの送信は行わず、実配信や業務効果も検証していません。