再送・順序・冪等性の設計
Webhook受信側を実装する開発者向けです。「同じイベントが複数回届いた」「古いイベントが新しいイベントの後に届いた」といった状況で正しく動作する受信側の作り方を説明します。
Webhookは便利な仕組みですが、ネットワークは不安定で、サーバーは落ちるものです。レシートローラーは「届かなかった」ときに自動で再送する仕組みを持っていますが、その結果として同じイベントが2回以上届くことと順序が前後することが起こり得ます。受信側はこれを前提に設計してください。
再送ポリシー
受信側が 2xx を返さなかった場合、レシートローラーは指数バックオフで再送を試みます。
| 回 | 送信タイミング |
|---|---|
| 1回目 | 即時 |
| 2回目 | 1分後 |
| 3回目 | 5分後 |
| 4回目 | 30分後 |
| 5回目 | 2時間後 |
| 6回目 | 6時間後 |
| 7回目 | 24時間後 |
合計で約32時間にわたり最大7回まで再送します。それでも成功しない場合はデッドレターとして記録され、開発者ポータルの「Webhook配信履歴」で確認できます。
再送の対象となるレスポンス
5xx(サーバーエラー)429(レート制限)- タイムアウト(10秒応答なし)
- 接続エラー(DNS / TLS / TCP)
再送の対象とならないレスポンス
2xx:成功とみなして再送しない3xx:リダイレクトは自動追従しない(失敗扱いだが再送もしない)4xx(401/410含む):受信側の永続的な拒否とみなして再送しない
5xx を返してください。4xx を返すと再送されません。
順序は保証されない
レシートローラーWebhookは、配信順序を保証しません。例えば次のような状況が起こり得ます。
receipt.issuedより先にreceipt.refundedが届くproduct.updatedがproduct.createdより先に届く- 1回目の配信が再送され、新しいイベントの後に旧イベントが届く
順序を扱う必要がある業務では、ペイロードに含まれる occurred_at(イベント発生時刻)を見て受信側で並べ替えるか、APIで最新状態を取り直す設計にしてください。Webhookを「最新状態の通知」ではなく「変更があったというトリガー」として扱うのが安全です。
冪等性の実装
同じイベントが複数回届いても結果が変わらないようにすることを冪等性と呼びます。レシートローラーは各イベントに一意の event_id(および X-RR-Event-Id ヘッダー)を付与しているので、これをキーにして重複処理を避けてください。
パターン1:処理済みIDを記録する
async function handleWebhook(event) {
// 既に処理済みなら無視
const exists = await db.processedEvents.findOne({ event_id: event.event_id });
if (exists) return;
// 業務処理
await processEvent(event);
// 記録(業務処理と同じトランザクション内が理想)
await db.processedEvents.insert({
event_id: event.event_id,
received_at: new Date(),
});
}
処理済みテーブルには TTL を設定し、古い記録を自動削除すると肥大化を防げます(再送期間32時間を考慮して、最低でも7日間は保持)。
パターン2:UPSERT で書き込む
業務処理が「あるレコードを最新状態にする」ものなら、UPSERT(INSERT or UPDATE)にすれば自然に冪等になります。
// 在庫イベントを受け取って、商品IDをキーに UPSERT
await db.inventory.upsert({
where: { product_id: event.data.product_id },
update: { quantity: event.data.quantity, updated_at: event.occurred_at },
create: { product_id: event.data.product_id, quantity: event.data.quantity },
});
このパターンでは、古いイベントが後から届いた場合に最新値を上書きしないよう、occurred_at 比較を入れる工夫が必要です。
パターン3:自然キーで判定
業務上の自然キー(例:receipt_id)が分かるなら、それで重複検知するのもシンプルです。event_id を使うパターンと併用すると堅牢になります。
順序ずれへの対処
同じエンティティに対する複数のイベントが順序ずれする場合の典型対策。
- occurred_at による上書き判定:受信側で持っている最終更新時刻より古いイベントなら適用しない
- バージョン番号:ペイロードに
versionがある場合、より大きい値だけを採用 - 状態遷移チェック:「refunded → issued」のように業務上ありえない遷移は無視
- API再取得:通知を受けたら最新状態をAPIで取り直す(順序を考えなくて済む)
デッドレターの扱い
7回再送しても成功しなかったイベントは「デッドレター」として保存され、開発者ポータル → 該当エンドポイント → 「配信履歴」タブで確認できます。各レコードには次の情報が含まれます。
- イベントID、種別、タイムスタンプ
- 各試行のレスポンスコードとボディ(先頭1KB)
- 「再配信」ボタン
受信側を修正したあと、「再配信」ボタンで手動で再送できます。期間は配信から30日間です。
よくあるアンチパターン
| やってしまいがちな実装 | 問題 |
|---|---|
処理が重いので非同期キューに投入してから 2xx を返さず、処理完了まで待つ |
10秒タイムアウトで再送ループに入る |
業務処理失敗時に 200 を返す |
再送されないのでイベントが取り逃がし |
| 冪等性なしで在庫を加減算 | 重複配信で在庫数が狂う |
| 順序ずれを考慮せず最新フラグを上書き | 古いイベントが新しい状態を踏みつぶす |
| デッドレターを監視していない | 障害に気づかず数日経過 |
推奨される受信パターン
- 署名・タイムスタンプ検証
event_idで冪等性チェック(処理済みなら即200)- ペイロードを内部キュー(SQS / Cloud Tasks など)に投入
- 即座に
200を返す(ここまで1秒以内が目標) - キューワーカーで業務処理を実行(リトライは内部で)
このパターンなら、受信エンドポイントは常に高速に応答でき、業務処理の失敗が再送ループに繋がりません。
関連ガイド
-
Webhookの登録方法レシートローラーの開発者ポータルでWebhookエンドポイントを登録する手順、購読イベントの選び方、テスト配信、複数エンドポイントの使い分け、削除と一時停止の方法を解説します。
-
SNS Webhookバイパス(LINEなど外部SNSのWebhook転送)レシートローラーがLINE等のSNSプラットフォームから受け取ったWebhookを、店舗側の同意のもとで開発者アプリへ転送する「SNS Webhookバイパス」機能の仕組み、設定方法、署名の取り扱い、注意点を解説します。
-
監視と失敗時の対応レシートローラーWebhookの配信履歴の見方、監視すべき指標とアラート設計、デッドレターの再配信、よくある障害パターンと復旧手順を解説します。
-
Webhookが届かないWebhookが受信エンドポイントに届かないときの原因切り分け。エンドポイント設定・購読イベント・到達性・署名検証失敗・ファイアウォールなどを順に確認する手順を解説します。
-
開発者向けヘルプ目次レシートローラー開発者向けヘルプ目次です。開発者申請、アプリケーション登録、OAuth認証とスコープ、実装ガイド(ウォレットアプリ・店舗向けWebhook・Survey API)、データ領域別ガイド、運用とセキュリティ、コミュニティ、トラブルシューティングまでをまとめています。