x402 acceptance-record: the buyer's signed verdict
Every x402 receipt so far is a statement by the seller. PR #4006 proposes the missing half: a record in which the buyer signs "accepted", "rejected" or "disputed" about one specific delivery. We read the spec, checked its vectors, and ran a stock-library signature check against all 127 of them.
The PR is a specification and a vector file. It changes no SDK and moves no funds. It was opened on 2026-10-27 by the Shodai team at CNS Labs, as x402-foundation/x402 #4006. When we read it on 2026-10-29 it was open, had no review, and its only comment was a Vercel deploy bot asking for authorization.
It matters because the next argument in agent payments is not about settlement. It is about what happens after the money moves and the output turns out to be wrong.
What the PR proposes
An acceptance record is the counterparty's signed decision about one delivery. The spec calls the signer of the delivery record the delivery issuer (for x402, the seller's server) and the decider the confirming party (usually the buyer). The decision is one of three strings: accepted, rejected or disputed.
The body is a closed JSON object with exactly eleven members: version, contextId, deliveryIssuer, confirmingParty, signer, authority, termsDigest, outcomeDigest, subject, disposition and issuedAt. A missing or extra member is an error. There is no extension container; new meaning needs a new body version.
Two signing forms carry the same body:
- EIP-712. Primary type
Acceptance, domain exactly{name: "x402 acceptance", version: "0"}. Ordinary accounts sign with ECDSA. Contract accounts are checked through ERC-1271 on the chain the message names. - JWS. Compact serialization, Ed25519, header exactly
alg: "EdDSA", akidandtyp: "x402-acceptance+jws". The payload must be the RFC 8785 (JCS) form of the body.
A record is not a standalone claim. It names its subject through a profile, and the only profile defined is x402ev/1, the execution-evidence format from PR #3682. The record's contextId is the 32-byte identity of that evidence record (evidenceRef), and its outcomeDigest must equal the digest the seller bound for the delivered content.
The terms are bound separately. termsDigest must equal the SHA-256 of the JCS form of a signed offer from the same seller, from the offer-and-receipt extension we covered in the receipts audit. The offer qualifies only if its network, asset, payee and amount match the evidence record.
Where it sits, and what it depends on
As of 2026-10-29 every neighbour of this PR is also open. We checked each one.
- #3682,
x402ev/1execution evidence: open, last updated 2026-10-08. This is the subject the record decides about. - #3783, delivery-receipt: open, last updated 2026-10-24. The acceptance spec lists it as "a future subject profile".
- #3604, Buyer Delivery Claim: open, last updated 2026-10-15. The spec calls it the closest prior work.
- TSC issue #4, the post-settlement accountability agenda item, from which the result vocabulary is taken: open.
So the record verifies against evidence that is not yet part of x402. The spec says so plainly: its status line reads "Draft, version 0. Not ratified; no x402 body has accepted it." Its identifiers, including the EIP-712 domain name and the JWS typ, are marked as draft and may change. The authors also say they will implement SDK support only after the specification is accepted.
The honest summary is that this is a well-specified building block for a stack that does not exist yet. That is still worth reading, because the design decisions are the part that will survive.
Five decisions worth reading closely
1. The EIP-712 domain has no chain
The offer-and-receipt extension fixes the domain chainId at 1 so the seller's key works on any network. An acceptance is signed by a buyer's wallet instead. The spec states that wallets commonly refuse to sign when the domain chain differs from the active network, and that the wallet its authors tested does.
So the domain carries only name and version. The account's chain is inside the signed message as signerChainId, and the verifier must check the signature on that chain and no other, "not the payment network and not the network a wallet was on". The spec also tells signer software to display signerChainId, because a wallet prompt may show a different active network beside it.
We confirmed the consequence directly. The baseline vector verifies against the two-field domain. Re-hashing the same message with chainId: 84532 added to the domain produces a different digest, and the recovered signer no longer matches. A signer that adds a chain, out of habit, produces a record no conforming verifier will accept.
2. A valid signature is never enough
Authority is a separate step. A verifier must not report verified unless the signer's right to decide for the confirming party, about this delivery, resolves. Two profiles exist.
payer/0 resolves when signer, confirmingParty and the evidence record's payer are the same string. declared-evidence/0 resolves only when the relying party's own trust inputs hold an entry matching the confirming party, signer, subject profile and subject reference. The record cannot carry that entry, and the spec says a seller naming a delegate does not create one.
That second rule is what keeps an agent's session key from silently speaking for a company. It also means that anything beyond "the payer signed it" requires the relying party to build trust data first.
3. The seller cannot accept its own delivery
The verifier must report acceptance_signer_is_issuer if the confirming party or signer equals the delivery issuer, or if the key that verified the record is listed for the issuer or signed the evidence record. The check covers keys as well as names, so a seller cannot launder an acceptance through a second identity that shares a key.
4. Silence means nothing
The spec reserves not_responded for "a response was due under the terms and none arrived" and states that this version never produces it. A missing record yields no_record, with no disposition and no conditions. Verifiers must not infer acceptance, rejection or lateness from it.
This is a deliberate refusal of the "silence is consent" pattern that auto-settle systems tend to adopt. Response windows are deferred to the next revision.
5. It binds terms, not the resource
The offer is matched on network, asset, payee and amount. The spec admits the gap: x402ev/1 does not define how its request URL hash is derived, so the offer's resourceUrl is not compared. "Two offers from one seller for different resources at the same amount, asset, payee and network both qualify."
For an LLM gateway this matters. A seller that quotes many models from one payee and one asset will often present several offers that match on those four fields. A verified acceptance would tell you the buyer accepted a delivery priced at that amount, not which model or which request produced it.
What we ran
The spec ships its vector file inside the PR. We fetched it from the PR head commit 6b3fc41 and ran three checks, plus the domain test described above.
Hash. The SHA-256 of the file is 8c20c905…16ec5, identical to the value in the spec header. Nothing drifted between text and corpus.
Anatomy. There are 127 vectors over 25 requirement cases: 70 EIP-712, 56 JWS and one with no record. The expected results are below.
malformed 74
unknown 30
verified 12
key_unresolved 10
no_record 1
Only 12 of 127 end in verified, 9.4% of the corpus. The rest probe refusals. The most common condition is acceptance_member_invalid (38 vectors), followed by signature_invalid (19). Of the 30 unknown results, 20 are evidence_unavailable and 8 are acceptance_authority_unresolved. This reads like a spec written by people who expect most records on the wire to be wrong.
Independent signature check. We wrote a signature-only checker with eth_account for EIP-712 and the cryptography package for Ed25519, and ran it on every record that parses and reaches the signature step. That is 114 vectors; 10 stop earlier at the key step and 2 are unparseable JSON by design. We compared "signature valid" with the corpus's signature_invalid condition.
95 agree. The 19 that differ all have an explanation. Four are contract-account cases that need an ERC-1271 call we did not model. Eleven are shape failures (an unclosed record, a non-JCS payload, numbers or text in the wrong encoding) where the signature itself may be fine but the record is rejected for another reason.
The remaining four are the interesting ones. In each, a stock library accepts a signature that the spec says to refuse:
- AR-C16-a:
sreplaced byn − swithvflipped. The signature still recovers the correct signer ineth_account. The spec requires0 < s ≤ n/2and refuses it. - AR-C16-b:
vwritten as 0 instead of 27. It recovers. The spec requires 27 or 28 and says not to normalize. - AR-C16-d: the signature hex in uppercase. It recovers. The spec requires lowercase hex.
- AR-C16-h (JWS):
Ris the identity point's encoding,S = k·a mod L. The cofactorless equation accepts it incryptography. The spec refuses anyRthat is not of order exactly L.
None of this is a bug in the libraries. They implement recover and verify, not this profile. The point is that a team which wires recover() into a handler will pass the happy path, pass most of the corpus, and still accept four classes of malleated input. We looked at the same family of rule, high-s rejection and strict encodings, in the exact EVM verification rules audit. A record that is stored, hashed or used as a database key by its signature bytes must not have two valid spellings.
Limits of what we checked
We did not run the authors' three checkers. We did not exercise ERC-1271 against a deployed contract wallet; neither does the spec, which tests contract accounts "with declared chain state only". We did not check the JCS serializations ourselves beyond what the vectors carry.
The authors also disclose that their three checkers all pass 127 of 127, and that documented interpretation differences remain, including a network-spelling example where the TypeScript checker still differs. Passing this corpus, in their words, does not settle inputs it does not cover.
What it means for LLM4Agents
Our product is a metered gateway. We are the delivery issuer in the spec's vocabulary: we return an answer, the buyer's agent decides whether it was what it paid for. Today that decision stays inside the buyer's own agent logs. This record is the first proposal that would let it live in a signed artifact both sides can verify.
Three consequences follow.
First, it is an enabler for dispute handling without a human. An agent that pays per call and gets a refusal, a truncated stream or the wrong model could emit a signed rejected or disputed record that names our own signed delivery evidence. That gives a reviewer, or an automated policy, a bounded set of facts to check instead of a free-text complaint.
Second, it is a threat to loose claims. If acceptance records become common, "the buyer received it" becomes checkable in both directions. Anything we say in our own reporting about deliveries has to be consistent with evidence we could be asked to sign.
Third, the resource gap above is ours to care about. Because the offer binding ignores the resource, a dispute over a response from one model could be backed by an offer for another model at the same price. Until the evidence format defines how it names the request, any record that touches our endpoints proves less than it appears to.
One thing it does not do is change settlement. A verified acceptance does not unwind a payment. The spec lists "release, refund or reputation" as consequences outside this version. On our side, nothing changes in the 402 flow, the facilitator path or billing.
Staying on the frontier
Concrete steps, in order. None needs a decision from us today.
One. Track the dependency chain, not just this PR. #4006 can only verify against #3682 evidence and an offer-and-receipt offer. If #3682 changes its evidence reference or its fields, the vectors change. We will re-check the four PR states and the vector hash when any of them moves.
Two. Build the strict-encoding habit now. When we write any verifier for signed agent artifacts, copy the profile rules that stock libraries skip: reject high-s, require 27 or 28, require lowercase hex, and reject small-order Ed25519 points. The corpus in this PR is a free test set for those rules.
Three. Decide what our delivery evidence would say before anyone asks. If we later emit x402ev/1 records, the fields that matter to a buyer are the content digest and the request binding. Settle how we derive a request hash first, because that is the exact gap the spec flags.
Four. Keep our own dispute process compatible. Whatever we offer buyers today, a signed message with a disposition, a subject reference and an outcome digest is the shape to aim for, so a future record can be attached without re-architecting.
Five. Stay out of the signing side until the spec is ratified. Identifiers like the domain name and the JWS typ are marked as draft. Shipping a signer now would mean shipping something that may not verify later. Reading and testing the corpus costs nothing; deploying against it is premature.
For the larger picture of how receipts, escrow and discovery fit together, see the extensions layer audit and the auth-capture escrow audit. Acceptance records are the buyer-side counterpart to the first and the missing trigger for the second.
Pay per call, in stablecoins, over an OpenAI-compatible API
One 402, one signature, 345+ models. No subscription, no minimum.
Register your agent