Integrate
Three ways in, depending on which side you are on
Recourse is additive. A seller keeps its existing x402 flow and changes where the money settles; a buyer keeps its existing client and gains somewhere to appeal. Neither has to adopt the whole protocol to get something out of it.
Call the adjudicator
Any agent buying from any x402 seller
The lowest-commitment entry point, and it needs no contracts at all. Send the SLA you agreed to and the response you got, and find out whether it honoured the terms — using the same code that runs inside the enclave. Agreeing with your own validator is worth nothing; agreeing with the adjudicator is what gets you refunded.
POST /v1/adjudicate # 0.01 HBAR via x402
{
"sla": { … },
"response": { "body": "<raw bytes>",
"contentType": "application/json" }
}
→ { "verdict": "REJECT",
"reasonCode": 4,
"failedClause": { "index": 4, "op": "numeric.lte",
"path": "$.spreadBps",
"note": "spread within 50bps" } }Sell under an SLA
Anyone running an x402-gated service
Publish what you promise, quote its hash in the 402, and point payTo at the escrow instead of your own account. Sign a hash of each response before you send it. In exchange, a buyer that disputes has to bring your signature, so you can never be accused of sending something you did not.
// in the 402
{
"accepts": [{ "payTo": "0.0.10380390", … }],
"recourse": { "paymentId": "0x…",
"slaHash": "0x…",
"slaUrl": "https://…/sla" }
}
// with the 200
X-Recourse-Response-Hash: 0x…
X-Recourse-Receipt: 0x… // sig over paymentId ‖ keccak(body)Buy with a refund path
Agents making unattended purchases
Settle into the escrow, bind the deposit to the terms, then check the response before you use it. If it fails, dispute with a bond and the verdict settles itself. The check is the point: a human notices a bad response and stops buying, and an agent making a thousand calls an hour does not.
const offer = await fetch(url); // 402 + slaHash
const sla = await verifySlaHash(offer); // refuse a mismatch
await settle(offer.accepts[0]); // into escrow
await bind(paymentId, seller, amount, slaHash, window);
const res = await fetch(url, { headers: payment });
const judgement = adjudicate(sla, evidence(res));
if (judgement.verdict === REJECT) {
await dispute(paymentId, responseHash, sellerSig, bond);
}Deployments
Testnet only
The contracts are unaudited and hold testnet value. Two limits worth knowing before you build on this: a seller could misstate its own serving time, since nothing on-chain attests a clock — the buyer comparing it against local time on receipt is the defence. And the adjudicator judges against a published SLA, so it has no opinion on whether data is true, only on whether it broke a promise. A seller that promises nothing cannot be held to anything, which is itself a useful signal when choosing who to buy from.