diff --git a/docs/specification/cart-rest.md b/docs/specification/cart-rest.md index 38dd6956..20ed4c7f 100644 --- a/docs/specification/cart-rest.md +++ b/docs/specification/cart-rest.md @@ -483,8 +483,10 @@ operations unless otherwise noted. * **Idempotency-Key**: Operations that modify state **SHOULD** support idempotency. When provided, the server **MUST**: 1. Store the key with the operation result for at least 24 hours. - 2. Return the cached result for duplicate keys. - 3. Return `409 Conflict` if the key is reused with different parameters. + 2. Return the cached result for duplicate keys whose request body matches the original. + 3. Return `409 Conflict` if the key is reused with a mismatched body. + See [Message Signatures — Idempotency Key Requirements](signatures.md#replay-protection) + for the full payload-matching contract. ## Protocol Mechanics diff --git a/docs/specification/checkout-rest.md b/docs/specification/checkout-rest.md index 3ef19d30..cb2e748d 100644 --- a/docs/specification/checkout-rest.md +++ b/docs/specification/checkout-rest.md @@ -1256,8 +1256,10 @@ operations unless otherwise noted. * **Idempotency-Key**: Operations that modify state **SHOULD** support idempotency. When provided, the server **MUST**: 1. Store the key with the operation result for at least 24 hours. - 2. Return the cached result for duplicate keys. - 3. Return `409 Conflict` if the key is reused with different parameters. + 2. Return the cached result for duplicate keys whose request body matches the original. + 3. Return `409 Conflict` if the key is reused with a mismatched body. + See [Message Signatures — Idempotency Key Requirements](signatures.md#replay-protection) + for the full payload-matching contract. ## Protocol Mechanics diff --git a/docs/specification/signatures.md b/docs/specification/signatures.md index fdb45f13..5b02d55d 100644 --- a/docs/specification/signatures.md +++ b/docs/specification/signatures.md @@ -483,9 +483,23 @@ Signature: sig1=:MEUCIQD...: | **Entropy** | Minimum 128 bits (e.g., UUID v4, 22+ char alphanumeric) | | **Uniqueness** | Per-client, per-operation type | | **Server storage** | Minimum 24 hours, recommended 48 hours | -| **On duplicate** | Return cached response, do not re-execute | +| **On duplicate (matching payload)** | Return cached response, do not re-execute | +| **On duplicate (mismatched payload)** | Reject with `409 Conflict` (REST) / `-32000` (MCP); do not execute | | **On storage failure** | Fail closed (reject request with 503) | +**Payload Matching:** Businesses **MUST** detect whether the payload of +a duplicate-key request matches the payload of the original by +comparing the SHA-256 hash of the raw body bytes — the same digest +RFC 9530 mandates as `Content-Digest`. When signing is in use, this +value is supplied in the `Content-Digest` header and the Intermediary +Warning above guarantees byte fidelity end-to-end; businesses persist +it alongside the idempotency key. For unsigned requests, businesses +compute the same digest from the received body bytes. Platforms +therefore **MUST** generate a fresh idempotency key whenever they +modify the request payload — including retries with modified payment +instruments, updated shipping addresses, swapped line items, or any +other change to the request body. + **Note:** The RFC 9421 `created` parameter is **OPTIONAL**. UCP handles replay protection at the business layer through idempotency keys, not signature timestamps. Key rotation (removing compromised keys from `signing_keys`) provides the mechanism diff --git a/docs/specification/split-payments.md b/docs/specification/split-payments.md index 164ae785..86e94813 100644 --- a/docs/specification/split-payments.md +++ b/docs/specification/split-payments.md @@ -173,8 +173,12 @@ MUST process each request as a fresh, full submission, without reference to prior requests or responses. > [!NOTE] -> **Idempotency & Correlation during Recovery Retries:** -> When retrying a partially authorized split payment with a modified set of payment instruments (e.g., replacing a declined card), the request payload changes and therefore **MUST** use a new idempotency key. Downstream systems cannot rely on the idempotency key to correlate the retry with previous partial authorizations; they must use the stable Checkout Session `id` to correlate and reconcile these distinct attempts. +> Each split payments submission is processed as a complete, +> self-contained request: a modified instrument set is a new submission, +> requiring a fresh [idempotency key](signatures.md#replay-protection). +> Split-payments state — including authorizations — does not persist +> between submissions; the unwind-on-failure requirement above +> enforces this. **Per-instrument reporting:** when a split is incomplete or has failed, the business MUST emit a `payment_failed` error for each failed