再送・順序・冪等性の設計

Webhook 再送 冪等性 順序 X-RR-Delivery

レシートローラーは、受信側が受け取れなかった配信を自動で再送します。その結果、同じイベントが2回以上届くことや、順序が前後することがあり、まれに届かないこともあります。この記事では再送の規則と、それを前提にした受信側の作り方を説明します。

成功と失敗

  • 受信側が 2xx を返せば成功です
  • それ以外はすべて失敗として再送されます。4xx(401 や 404 を含む)も 5xx と同じ扱いです
  • 10秒以内に応答がない場合や、DNS・TLS・接続のエラーも失敗です。配信ログでは httpStatus が 0 になります

再送のタイミング

試行 送るタイミング
1回目イベントの発生直後
2回目1回目の失敗の1分後
3回目2回目の失敗の10分後
4回目3回目の失敗の1時間後
  • 最大4回で、最後の試行は最初の送信から約71分後です(各試行の待ち時間10秒を除く)
  • 再送のボディ、署名、X-RR-Delivery は最初の送信と同じです
  • 4回とも失敗すると、そのWebhookは無効(active: false)になり、以後のイベントは送られません。1つのイベントで4回失敗しただけで、Webhook全体が止まります
  • 無効になっている間に起きたイベントは保留されず、有効に戻してもあとから届きません。過去のイベントを手動で再送する機能もありません
  • 一時停止(active: false)にしても、すでに再送待ちになっているイベントの再送は止まりません

再送は保存されていない

再送の待ち時間はレシートローラーのサーバー上で保持されており、キューやデータベースには保存されていません。待っている間にサーバーが再起動すると(デプロイなど)、そのイベントの残りの再送は行われず、Webhookも無効になりません。配信ログには最後に失敗した試行が残るだけです。

そのため「再送があるから必ず届く」とは考えず、取りこぼしが困る連携では定期的にAPIで取り直して突き合わせてください。売上なら取引一覧APIで期間を指定して取得できます。

順序は保証されない

イベントはそれぞれ独立に送られ、再送はあとから届きます。次のようなことが起こり得ます。

  • 受注の order.updated が order.created より先に届く
  • 1回目に失敗した売上が、あとの売上より遅れて届く

順序が大事な処理では、Webhookを「変更があった」という合図として扱い、APIで最新の状態を取り直すのが安全です。ボディの timestamp はレシートローラーがイベントを送り出した時刻で、再送でも変わらないので、同じ受注について古い通知で新しい状態を上書きしないための比較に使えます。

重複の除き方

同じイベントが2回以上届くのは、主に受信側が処理を終えたのに 2xx を返せなかったとき(10秒を超えた、途中でエラーになった)です。どの配信にも、イベントごとに一意な X-RR-Delivery ヘッダー(ボディの id と同じ値)が付いているので、これをキーにします。

async function handle(deliveryId, event) {
  // 処理済みならそのまま 2xx を返す
  const inserted = await db.processedDeliveries.insertIfAbsent({
    delivery_id: deliveryId,
    received_at: new Date(),
  });
  if (!inserted) return;

  await processEvent(event);
}
  • 処理済みの記録は、再送が終わる約71分より長く、少なくとも数時間は残します
  • 処理済みの記録と業務の更新を同じトランザクションで行えると、途中で失敗したときに記録だけが残ることを防げます
  • 同じイベントを購読しているWebhookを複数登録している場合も、どのWebhookからの配信にも同じ X-RR-Delivery が付くので、まとめて1回だけ処理できます

業務上のキーで書き込みを冪等にする方法も併用できます。売上なら data.id({terminal_id}|{yyyy-MM}|{transaction_id} の形)、受注なら data.order_id をキーにして上書き保存すれば、同じ内容が2回届いても結果は変わりません。

おすすめの受け方

  • 署名を検証する
  • X-RR-Delivery で重複を確認し、処理済みならすぐ 200 を返す
  • ボディを自分の側のキューやテーブルに保存する
  • すぐに 200 を返す(10秒を大きく下回るように)
  • 重い処理や外部サービスへの送信は、保存したデータから別の処理で行い、失敗したら自分の側でやり直す

こうすると、外部サービスが止まっても受信は失敗せず、4回の失敗でWebhookが無効になることも避けられます。

よくある失敗

作り 起きること
重い処理を終えてから応答する10秒を超えて失敗になり、同じイベントが再送される
一時的な失敗のたびに 5xx を返し続ける1つのイベントで4回失敗するとWebhookが無効になり、以後のイベントがすべて止まる
重複を除かずに数量や金額を加算する再送で二重に計上される
届いた順に状態を上書きする古い通知が新しい状態を上書きする
Webhookだけを頼りにする無効化中やサーバー再起動時のイベントに気づけない

関連ガイド

公開日: 2026-04-27 更新日: 2026-10-26
タグ
API (26) OAuth (21) Android (13) iOS (12) Webhook (9) api (6) oauth (5) クイックスタート (5) トラブル (5) Node.js (4)