Per-request payment protocols like x402 handle fixed prices cleanly: the server quotes an amount, the client pays it, the resource unlocks. The interesting failure mode appears when the price moves - freight, compute spot rates, anything from a live market. Then the payment isn't for a request, it's for a quote, and a quote expires.
The usual design reaction is to pick a round TTL - thirty seconds, sixty seconds, five minutes - and treat it as a UX setting. It is not. It is one of the few numbers in the system that directly multiplies your unit cost.
The timing that decides it
Take an agent flow: fetch rates, pick one, maybe ask a human, then pay. Measure the distribution of the gap between "rates returned" and "payment sent". Call its median T.
If the lock TTL is L and L < T, more than half of your quotes expire before they are paid. Every expiry means a re-quote, and a re-quote is a fresh market price, not the one you displayed.
So expected price slippage is a product of two numbers you can both measure:
expected_slippage ≈ P(gap > L) x drift_per_requote With a 75-second median think-time and a 60-second lock, P(gap > L) is above 0.5. At 2% drift per re-quote that is roughly 1% of order value lost to timeouts - usually far more than the payment fee the whole architecture was built to reduce.
Three consequences that are easy to miss
1. The TTL should be set from the think-time distribution, not the payment flow. If your agent is fast, a short lock is genuinely cheap. If a human is in the loop, the honest number is the 90th percentile of human review time, and the lock should probably be longer than feels natural - a longer lock has a cost too, but it is a cost paid by the seller in inventory risk, which is why it is usually priced into the quote rather than exposed.
2. Re-quote rate is the metric to instrument. Not payment success rate. A system with a 98% payment success rate and a 60% re-quote rate is losing on price on most orders and will look perfectly healthy on every dashboard you have.
3. Idempotency has to be keyed on the quote, not the request. If the retry key is derived from the HTTP request, a retry after a timeout either double-pays or is deduplicated against a different price. Keying on quote_id makes a retry against a consumed quote fail cleanly, which is the behaviour you want: the client learns the quote is gone instead of learning it was charged twice.
Why this generalises past freight
Every moving-price API has this shape eventually. GPU spot pricing, live ad auctions, anything with a rate that updates faster than an agent can think. The protocol work is done - 402 challenges and settlement rails exist. What is left is arithmetic that most implementations skip:
- what is the median gap between quote and payment,
- what fraction of quotes expire,
- and what does that fraction cost per order.
Those three numbers are cheaper to compute than the migration, and they decide whether the migration was worth doing.

