Dub webhook署名検証:raw bodyと再送の境界をNode.jsで確認する
このトピックを深掘りする
SaaSの導入・連携
AIに仕事の続きを渡すために、会議の原文、未完了の宿題、確認できない項目をどう残すか。導入判断と連携実装を掘り下げる。
全6本中 3 本目。
次に読む記事
Slackのhuddleを録り、毎晩22時にAIへ渡す:Circlebackの設定6か所
AIに仕事の続きを渡すためのCircleback運用記録。Slack huddle、22時の同期、元の発言と未完了ToDo、採用する条件を具体化。運用経験・公式仕様・ローカル試験の根拠も示す。
Circlebackの会議アクションをタスクへ渡す:Webhookの署名・担当者・重複処理を検証
CirclebackのWebhookからToDoを保存するコードを検証。署名・担当者なしに加え、SQLiteの重複防止、古い再送で完了状態が戻る条件、保存先の状態を別に保存してDONEを保つ方法を合成データで確かめた。
Dubの紹介リンクを日英で計測する:UTM生成で紹介IDを壊さない実装と検証
紹介リンクへUTMを足すと、既存の値が同じでもURL表記が変わる。空白とチルダの再現コード、署名付きURLへ適用する限界、クリックと確定報酬の区別をまとめた。
シリーズ全体の流れを見ながら、次に読む記事へ進めます。 初めての方はホームへ →
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への送信は行わず、実配信や業務効果も検証していません。