Show Me Your Settlement State Machine
There is a bug report sitting in the tracker for AP2, Google's agentic payments protocol, that every pay-per-call builder should read twice.
There is a bug report sitting in the tracker for AP2, Google's agentic payments protocol, that every pay-per-call builder should read twice. The reference sample marks a payment token as used before settlement is confirmed. When the settlement call fails, nothing puts the token back. No funds moved, and the buyer's next attempt bounces off a token the store already considers spent. Nothing in the protocol is broken. The sample's ordering is. And in machine payments, the ordering is the product.
The bug, precisely
Reference implementations carry default authority. When the most resourced player in the authorization layer publishes a sample, integrators treat it as a vetted path and copy the shape into production. That is why this one matters more than its severity label suggests.
AP2 · Issue #308
The x402 human-not-present sample in AP2’s merchant agent marks a payment token as consumed, and allocates an order ID, before calling the payment service provider to settle. On the failure path there is, in the reporter’s words, “no compensating write.” A failed settlement leaves the token permanently marked used. The reporter confirms no funds moved and no on-chain transaction was submitted. The buyer’s legitimate retry is rejected as already spent, and the merchant inherits a reconciliation problem for every payment that failed between those two lines of code.
Source: github.com/google-agentic-commerce/AP2/issues/308 · flagged by Eric Tsang’s Agent Payments Weekly, which called “show me your settlement state machine” the new standard diligence question.
Run the same shape in the other direction and it gets worse. A sample that treats “authorization accepted” as “payment complete” will release the goods on acceptance. If settlement then fails on congestion, gas, a contract revert, or a competing transaction, the merchant has shipped against money that never arrived. Same one-bit state, opposite victim.
One bit where four states belong
The root cause is not a typo. It is a modeling decision: collapsing two different facts into a single used flag. “The payer authorized this payment” and “this payment settled on chain” are separate events, separated by a network round trip that can fail in a half dozen ways. Any implementation that stores them as one bit has already decided that failures between the two will corrupt its books.
The correct shape is boring and explicit. A payment authorization moves through states: verified, evaluated, settlement intent, then settled or abandoned. Every failure path writes the state it failed in. Nothing is inferred, and nothing is optimistic. This is the machinery invoice systems and card processors have carried for decades, and it does not stop being necessary because the payer is an agent.
The ordering we run in production
InsumerAPI’s condition-based access endpoints accept x402 pay-per-call beside the ordinary API key. An agent with a wallet and USDC on Base can call api.insumermodel.com with no key, no email, and no human in the loop: the transaction sender wallet is the identity, the payment is the auth. Here is the order of operations that branch runs on every paid call, and the guarantee that ordering buys.
Price is exact. The 402 challenge quotes a price; the signed payment must match it to the atomic unit. Overpayment is rejected, not kept.
Signature, then solvency. The payment signature is verified first; the payer’s identity is trusted only after it checks out. The facilitator then confirms the wallet can actually fund the transfer.
One authorization, one spend. The payment’s nonce is claimed exactly once. A replayed authorization is refused before any work happens.
Evaluation runs against a buffer. The request is evaluated in full, but the response is held. Nothing reaches the caller yet. If evaluation refuses the request, the caller gets that error verbatim and no money moves.
Settlement gates the response. Only a 200 proceeds to settlement, and the settlement record moves through its states explicitly: intent, then settled or abandoned. If there is not enough time left in the payment’s validity window to settle safely, the call refuses up front and says so: you have not been charged. If the facilitator reports failure, the record is marked abandoned, the caller is told nothing was charged, and a fresh payment is required.
Evidence rides in-band. Only after settlement confirms does the buffered response flush, carrying the settlement transaction hash in the
X-PAYMENT-RESPONSEheader and the response body. The caller does not have to take our word that settlement happened. The hash is independently checkable on Base against the expected transfer: the parties, the amount, the asset.
The guarantee is symmetric, and it is the whole point: the payer is charged only for a successful answer, and the payer sees the answer only after the payment settled. A failed settlement cannot strand a burned token, because the authorization’s state records exactly where it stopped. A failed evaluation cannot take money, because settlement never started. Neither side of the trade is ever holding the bag while the other side has both.
Questions to ask any pay-per-call implementation
The diligence framing is right, and it generalizes. Whether you are auditing a vendor, a sample you are about to copy, or your own code, the same five questions separate an accounting system from an optimistic one:
Is “used” one bit or a state machine? If a single flag stores both authorization and settlement, the failure paths between them are already lost.
What happens when settlement fails? Look for the compensating write. If there is none, every failed settlement strands a token, a buyer, or a merchant.
Does the caller see the result before settlement confirms? If yes, someone is carrying settlement risk in that gap. Card networks made that a legitimate design by naming the party who carries it. If nobody in your flow has explicitly assumed the risk, the merchant is extending credit to anonymous agents and calling it latency.
Where is the settlement transaction hash in the response? If the caller cannot independently confirm settlement on chain, they are trusting a log line.
Can one payment authorization be spent twice? Replay handling should refuse before any work happens, not reconcile after.
Notice what these questions have in common with the rest of our writing on agent payments: the fix is always a verifiable answer standing in a specific position in the flow. Authorization before payment is a signed boolean, which we covered in the x402 authorization gap. Proof around the transaction is a signed attestation on both sides, covered in the pre and post pattern for agent commerce. Settlement confirmation inside the flow is what this post covers. The three layers of trust frame holds: each layer needs its own checkable answer, and no layer can borrow another’s.
Samples become production. Choose which sample.
The AP2 bug will get fixed; it is one write on one failure path, and the maintainers are capable. The durable lesson is about defaults. Reference code teaches ordering by example, and ordering is exactly the thing a hurried integrator copies without reading. If the sample you copy collapses settlement into a bit, so does your checkout.
Ours is live and inspectable from the outside. Send a paid call to api.insumermodel.com and watch the behavior: an exact quote, a refusal to keep overpayment, an answer that arrives only with its settlement hash attached, and a plain-English “you have not been charged” on every path where the machine stopped short. Condition evaluation across 38 chains, settlement in USDC on Base, and a state machine you can interrogate one request at a time. That is what we think reference behavior should look like.


