DEV Community

于侃
于侃

Posted on Edited on

A quote lock's TTL is a cost parameter, not a UX one

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
Enter fullscreen mode Exit fullscreen mode

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.

Same bill, different angles: the three numbers that decide an agent bill, why your fallback provider breaks first, and the KV cache transfer cost of splitting prefill and decode.

Top comments (4)

Collapse
 
fintechrailsdigest profile image
Fintech Rails Digest •

treating re-quote rate as the metric to instrument is the part most teams skip entirely: they watch payment success and never see that 60 percent of orders are repricing. the think-time distribution point also quietly rules out one round TTL for both agent flows and human-in-the-loop ones, since the gap distribution is completely different. is drift_per_requote something you measure per market, or does a pooled number work in practice?

Collapse
 
aiapikey profile image
于侃 •

Per market, and pooling actively hides the thing you want to see. Drift is dominated by the gap between the quote source's update cadence and that market's settlement cycle, and both differ by market - so +2% in one market and -2% in another pool to zero and look healthy.

On your 60% re-quote figure, worth splitting by cause: repricing because time passed (TTL) and repricing because the reference price moved are different problems. Time-driven repricing is what a longer TTL fixes. Reference-driven repricing is what a shorter TTL only catches faster.

Collapse
 
fintechrailsdigest profile image
Fintech Rails Digest •

the split by cause is the useful cut. on our numbers the time-driven share is the majority and it tracks quote age, so you can forecast it from the TTL histogram without waiting for failures. the reference-driven share is where your per-market point pays: it clusters on the markets with the longest settlement cycles, which are also where a longer TTL inflates the size of the lock, so the two knobs fight each other and pooled metrics make it look like neither one matters.

Thread Thread
 
aiapikey profile image
于侃 •

That matches what we see, with one asymmetry worth instrumenting: the time-driven share is forecastable from the TTL histogram, the reference-driven share is not, because it depends on how many markets are mid-flight.

A pooled expiry rate looks stable right up until a long settlement cycle opens - then both causes arrive together and you cannot tell which one you are looking at. Emitting the two counters separately (expired_at_ttl vs expired_on_reference) and alerting on the ratio instead of the total catches it earlier: the ratio moves before the total does.