冪等キーとは?Idempotency-Keyの発行・保存・再生の実装と二重課金の止め方を解説【2026年版】
冪等キーとは、クライアントがリクエストごとに発行し、再送されたリクエストが初回と同じ意図であることをサーバへ伝える一意な識別子です。HTTPでは Idempotency-Key というリクエストヘッダに載せて送る形です。決済の確定や注文の作成のように、二度実行されると被害が出る操作へ後から安全な再送を足すための道具で、冪等性という性質そのものの定義を実装へ落とし込む部品にあたります。この記事で扱う範囲は、鍵を受け取ったサーバが何を記録して何を返すのか、保存テーブルとスコープと有効期限をどう決めるのか、送信側は鍵をどこで作るべきか、そして鍵を持ち込むべき操作の線引きまでです。読み方や数式、HTTPメソッド別の可否は性質側の記事に譲ります。
まとめ|冪等キーが買うのは「返事が来ない時にもう一度押せる」状態
この仕組みが売っているのは1つだけです。ネットワークが切れて応答が届かなかった時に、処理が通ったのか通っていないのか分からないまま、同じリクエストをもう一度投げられる状態です。送信側は成否を確かめる問い合わせAPIを作らずに済み、受け取る側は二重処理の後始末をしなくて済みます。
対価も明確です。サーバ側に鍵の記録用ストレージが1つ増え、そこへの書き込みが処理本体の前段に挟まります。記録は永久には持てないので有効期限の設計が要り、期限切れ後の再送は素通りするという穴も残ります。判断としては、二重実行が金銭や在庫に直結する書き込みAPIに置き、参照系や、重複しても実害の小さい記録系には持ち込みません。標準としては、ヘッダ名を定めたIETFのドラフトが2026年8月時点で失効したままで、事実上は決済APIの実装慣行が基準になっています。
冪等キーとは|クライアントが発行する「同じ意図」を示す識別ラベル
まず鍵が何を担っていて、何を担っていないのかを分けます。ここを曖昧にしたまま実装すると、鍵を送っているのに二重処理が起きるという状態になります。
鍵は目印にすぎず、二重処理を実際に止めるのはサーバ側の記録処理
冪等キーそのものは、ただのランダムな文字列です。それ自体には何の力もありません。二重処理を止めているのは、サーバが「この鍵はもう受け付けた」と記録し、次に同じ鍵が来た時に処理本体を呼ばずに記録済みの応答を返す、という受け側の実装です。鍵は、2つのリクエストが別々の意図なのか同じ意図の再送なのかを、サーバが判別するための目印を提供しているだけになります。
この分担を押さえると、設計の焦点がどこにあるかが決まります。クライアント側でやることは「一意な値を作ってヘッダに載せる」だけで、実装の中身はほぼサーバ側にあります。逆に言えば、鍵を受け取るヘッダを用意しただけで記録と再生を作っていないAPIは、鍵に対応しているとは言えません。
鍵に使う値|UUIDv4を定番とし、長さと中身に制約を置く理由
値の作り方は、衝突しない乱数であれば形式は問われません。IETFのドラフト(draft-ietf-httpapi-idempotency-key-header-07)は、UUIDまたは同等のランダム識別子の使用を推奨し、ヘッダ値を文字列型と規定しています。Stripeのリファレンスも v4 UUID を推奨しており、鍵の長さは255文字までです。実装では128ビット相当の乱数を素直に使えば足ります。
置いてはいけない中身が1つあります。メールアドレスや会員番号のような、その人を特定できる値です。鍵はログやエラー通知や監査記録へ広く残るため、識別子をそのまま入れると個人情報の保存場所が意図せず増えます。Stripeのリファレンスも、機微なデータを鍵に使わないよう明記しています。業務側の値を混ぜたい場合は、乱数と組み合わせたハッシュにしておくと痕跡が残りません。
取引IDやリクエストIDとの違い|作る主体と再送時の変化で見分ける
似た識別子との混同が起きやすいので、境界を引いておきます。判別の軸は「誰が作るか」と「再送で値が変わるか」の2つです。
| 識別子 | 作る主体 | 再送時の値 | 主な用途 |
|---|---|---|---|
| 冪等キー | クライアント | 初回と同じ | 再送の同一性判定 |
| リクエストID | サーバやゲートウェイ | 毎回変わる | ログ追跡と障害調査 |
| 取引ID | サーバ | 初回の値を返す | 作られた資源の参照 |
要点は真ん中の列です。再送しても値が変わらないのは冪等キーだけで、これがあるから初回と再送を結び付けられます。トレース用のリクエストIDを重複排除に流用しようとする設計を見かけますが、経路ごとに振り直されるため再送の判定には使えません。
サーバ側の処理フロー|実行より先に鍵を予約する5段階と例外処理
受け側の実装は5つの段階に分かれます。順序を1か所でも入れ替えると穴が開くため、ここが実装の中心です。API設計ガイドが示す一般則と同じく、例外の扱いを先に決めておくと後戻りが減ります。
第1段階|処理を始める前に鍵の行を作り、一意制約で場所を押さえる
最初にやるのは、処理本体の実行ではありません。鍵を保存するテーブルへ「この鍵で処理中」という行を挿入し、その挿入に一意制約をかけておくことです。挿入が通れば自分が初回で、一意制約違反で弾かれたら誰かが先に同じ鍵を受け付けています。この判定を、確認してから挿入するという2段階ではなく、挿入の成否そのもので行う点が肝心です。
順序を逆にした実装、つまり決済を実行してから鍵を記録する実装には、実行と記録の間にプロセスが落ちた場合の穴が残ります。課金は通ったのに記録がないため、再送が2回目の課金として素通りする構造です。先に予約しておけば、落ちても行は残り、再送は処理中として扱えます。
第2段階から第4段階|実行し、応答を記録し、状態を確定させる
予約が取れたら処理本体を実行します。完了したら、返すつもりのステータスコードと本文を鍵の行へ書き込み、状態を処理中から完了へ移します。この書き込みと業務データの更新を同じトランザクションに入れられるなら、そうしておくと状態のずれが起きません。別ストアに鍵を置く場合は、業務データを確定してから鍵の行を更新する順序にします。
記録するのは成功応答だけではありません。Stripeのリファレンスは、初回リクエストのステータスと本文を成否によらず保存し、以後の同一鍵には500系の応答も含めて同じ結果を返すと記載しています。失敗も再生するのは、「同じ鍵での再送は必ず同じ答えを返す」という約束を保つためです。ただし入力検証で弾かれた場合や並行実行中の別リクエストと衝突した場合は結果を保存せず、再送してよい扱いになっています。処理が始まっていない以上、記録すべき結果もないという整理です。
第5段階|再送への応答を決め、409と422を状態別に切り分ける
2回目以降の扱いは、鍵の行の状態で3つに分かれます。ドラフトが定義する応答は次のとおりです。
- 行が完了状態なら、記録済みのステータスと本文をそのまま返す(処理本体は呼ばない)
- 行が処理中なら、初回が完了する前の再送として409 Conflictを返す
- 行はあるが今回のペイロードが初回と違うなら、鍵の使い回しとして422 Unprocessable Contentを返す
3つ目の判定には、初回のリクエスト本文のハッシュを鍵の行へ一緒に保存しておく必要があります。同じ鍵で金額だけ変えたリクエストが来た時、これがなければ初回の応答を返してしまい、送信側は2件目が通ったと誤解するでしょう。Stripeも、届いたパラメータを初回と比較して異なればエラーにすると記載しており、実装慣行としても定着しています。なお鍵を必須にする設計なら、鍵の欠落には400 Bad Requestを返します。
保存テーブルの設計|スコープ・保存内容・有効期限を決める判断基準
鍵の記録は、置き場所を決めれば終わりではありません。区切り方と消し方まで決めて、はじめて運用に載ります。
スコープ|鍵だけで引かず、テナントとエンドポイントの単位で区切る
一意制約を鍵の値だけに張ると、利用者をまたいだ衝突が起きます。他社が偶然同じ鍵を送ってきた時、無関係な相手の応答を返してしまうためです。乱数の衝突確率は無視できても、鍵を連番や固定文字列で作るクライアントは実在します。制約は、テナントIDやAPIキーIDと鍵の組で張るのが安全です。
エンドポイントを含めるかは、APIの公開範囲に応じて方針が分かれる部分です。含めれば同じ鍵を別APIへ流用できて事故が減る反面、「1つの業務操作に1つの鍵」という原則からは外れます。判断基準は、公開APIならテナントとエンドポイントと鍵の3つ組、社内向けならテナントと鍵の2つ組です。公開側は使い方を制御できないぶん、区切りを細かくしておくほうが被害が小さくなります。
保存する中身|応答の再生に必要な状態と本文だけを選んで持つ設計
行へ持たせるのは、状態・リクエスト本文のハッシュ・応答ステータス・応答本文・作成時刻の5つが基本です。応答ヘッダを再生したい場合は、位置情報を返すヘッダなど業務上意味のあるものだけを選んで持ちます。すべてのヘッダを保存すると、日時やトレースIDまで巻き戻ることになり、かえって不自然な応答になります。
応答本文が大きいAPIでは、本文全体ではなく作られた資源のIDだけを保存し、再送時にその資源を読み直して応答を組み立てる方式も選択肢です。保存量は減るものの、資源が後から更新されていると初回と違う本文を返す点は許容する必要があります。金額や状態が動く資源では、本文をそのまま保存するほうが約束を守れます。
有効期限|24時間の慣行を基準に、期限切れ後の素通りへ備える
記録を永久に持つ設計は取れません。行が増え続け、一意制約の索引が肥大化します。Stripeは、鍵を24時間以上経過した後に削除してよいこと、削除後に同じ鍵が来たら新規リクエストとして扱われることを記載しています。この24時間という値は、クライアント側の再送が現実的に続く時間の見積もりです。
期限を過ぎた鍵での再送は防止が効かない、という点は設計上の穴として残ります。ここを塞ぐのは鍵ではなく、業務側の一意制約です。注文番号や外部取引IDに一意制約を張っておけば、鍵の期限が切れた後の重複も最後の砦で止まります。鍵は「短期の再送を吸収する層」、業務制約は「恒久の重複を止める層」と役割を分けて、両方を置くのが実務的な構えです。
送信側の設計|鍵を作る位置とリトライの組み合わせで二重送信を防ぐ
ここからは送る側です。受け側を正しく作っても、送信側の鍵の作り方を誤ると防止はまったく効きません。上位の解説が触れない論点なので、独立して扱います。
鍵はリトライループの外側で業務操作ごとに1回だけ作って保持する
最も多い誤りは、リクエストを送る関数の中で鍵を生成することです。リトライのたびに新しい鍵が振られるため、サーバから見ればすべて別の意図となり、素通りして二重課金が起きます。鍵は業務操作が始まった時点で1回だけ作り、その操作に対する再送の全てで同じ値を使い回します。
作る位置は、リトライを回す層よりさらに外側が安全です。画面の送信ボタンが押された時点、あるいはジョブが投入された時点で鍵を確定させ、リクエスト本文と一緒に保持する設計です。プロセスが再起動しても同じ鍵で再開したいなら、鍵自体を業務データ側に保存しておきます。送信元が落ちて再開したケースでも、保存された鍵を使えば重複が防げます。
再送の間隔と回数は指数バックオフを担うリトライ側の設計に委ねる
鍵は「何度送っても安全か」を担保するだけで、「いつ何回送るか」は決めません。ここは指数バックオフとリトライ予算の設計の担当領域で、鍵と組み合わせて初めて再送が実務に耐えます。鍵があるからと無制限に再送すると、今度は相手のAPIを押し込む側になります。
409が返ってきた時の扱いだけは、鍵側の事情を汲む必要があります。これは初回がまだ処理中という意味なので、即座に投げ直さず少し待ってから確認するのが妥当です。上限超過で429と再試行の待ち時間を返す設計を採るAPIなら、指示された待ち時間に従います。422が返った場合は再送しても結果は変わらないため、鍵の使い回しを疑ってコード側を直します。
決済APIとメッセージ受信での実際|鍵が必要になる処理境界を見極める
実装の当たりを付けやすいよう、鍵が実際に使われている2つの領域を見ます。
決済API|通信断後の二重課金を止める最後の砦として組み込む
鍵が最も定着しているのは決済です。カード決済の確定処理は、通信が切れた時に成否が分からないという状況が日常的に起き、確認のためだけに問い合わせAPIを叩く運用は現実的ではありません。Stripeは全てのPOSTで鍵を受け付け、GETとDELETEに付けても効果がないと記載しています。参照や削除はもともと繰り返しても状態が変わらないため、鍵を足す意味がないという整理です。
国産の決済APIを含め、提供側が鍵に対応しているかは事前確認が必要です。国産決済APIの実装フローと選び分けで扱ったように、事業者ごとに再送時の扱いは異なります。対応がない場合は、自社側で取引テーブルに外部取引IDの一意制約を張り、送信前に自前の重複判定を挟む構えになります。
メッセージ受信|at-least-once配信の重複を捨てる側
もう1つは非同期のメッセージ処理です。多くのキューは少なくとも1回の配信を保証する方式を採るため、同じメッセージが2回届くことを前提に受け手を作る設計です。ここでの鍵は、送信側が付けたメッセージIDやイベントIDがそのまま役割を果たします。受け手は処理済みIDのテーブルへ一意制約付きで挿入し、違反したら黙って捨てる、という同じ形になります。
発行側で重複を減らす取り組みはDB更新とイベント発行を一致させる実装方式の担当で、こちらは受信側の話です。両方を組み合わせても重複はゼロにならないため、受け手の重複排除は省けません。HTTPの鍵と違うのは、有効期限をイベントの再配信期間へ合わせる点で、キューの設定より長めに持たせておきます。
採用の条件と見送る場面|冪等キーを持ち込むAPIの境界線を引く
ここは判断を言い切ります。鍵はストレージと前処理を1段増やす仕組みなので、全てのAPIへ一律に足す設計は取りません。
持ち込む条件は3つです。1つ目は、二重実行が金銭・在庫・通知など取り返しのつかない副作用を生むこと。2つ目は、その操作が新規作成型で、同じ内容を送り直しても勝手に上書きされる作りではないこと。3つ目は、呼び出し元がネットワーク越しで、応答を取りこぼす可能性があることです。決済の確定、注文の作成、ポイントの付与、外部への送信処理はこの3つを満たします。
見送るのは、参照系のAPI、状態を絶対値で指定する更新API、そして重複しても実害が小さい記録系です。絶対値で指定する更新は同じ内容を何度送っても結果が動かないため、鍵を足しても防いでいるものがありません。閲覧ログのような記録系は、鍵の管理コストのほうが重複の害を上回ります。社内の同期処理で、呼び出し元が1つに固定され再送しないと分かっている場合も対象外です。
迷う場合は、その操作が失敗した時に人手で復旧できるかを起点にすると判断がぶれません。手で戻せない操作にだけ鍵を置きます。既存APIへ後から鍵を足す改修や、決済と在庫をまたぐ連携の設計は、API開発・システム連携としてご相談を承っています。
よくある質問
冪等キーは誰が発行するのですか?
クライアント側です。サーバが発行して返す方式にすると、鍵を受け取る前に通信が切れた場合に再送のしようがなくなり、仕組みが成り立ちません。送る側が業務操作の開始時点で一意な値を作り、その操作に対する全ての再送で同じ値を使います。サーバは受け取った値を記録するだけの立場になります。
同じ鍵で内容の違うリクエストを送るとどうなりますか?
ドラフトの定義では422 Unprocessable Contentが返ります。サーバは初回のリクエスト本文のハッシュを保存しており、今回の内容と一致しなければ鍵の使い回しとみなして拒否する仕様です。この比較を実装していないと、金額だけ変えた2件目に対して初回の応答を返してしまい、送信側は通ったと誤解します。
有効期限はどのくらいに設定すべきですか?
決済APIの慣行に合わせて24時間前後から始めるのが無難です。クライアント側の再送が現実的に続く時間を上回っていればよく、それより長く持つとテーブルと索引が膨らみます。期限を過ぎた再送は素通りするため、業務側の一意キーにも制約を張って二層で止める構えにします。
Idempotency-Keyヘッダは標準仕様ですか?
2026年8月時点では標準化されていません。IETFのhttpapiワーキンググループでドラフトが進められましたが、07が最終版のまま失効しています。ヘッダ名と挙動は決済APIの実装慣行として広く共有されている状態なので、公開APIでは仕様書に自社の扱いを明記しておく必要があります。
鍵を使わずに二重処理を防ぐ方法はありますか?
業務上の一意キーにデータベースの一意制約を張る方法があります。注文番号や外部取引IDが重複したら制約違反で弾く形で、鍵より単純に組めます。ただしこの方式では初回と同じ応答を返せず、再送側にはエラーが返るため、成否の確認は別途必要です。応答まで揃えたい場合に鍵を選びます。