Skip to content
Undo

Docs

Everything a developer needs, on one page.

How agents buy through PayBox, the API, the Native middleware and the contracts.

Overview

Undo sits between an agent and any paid x402 service. The buyer signs one USDC authorization. The money goes into Coinbase Commerce Payments escrow on Base, the merchant is called, and the payment settles when the dispute window closes. Until then, a refund is one request away.

There are two modes. Covered works with every x402 service today: Undo pays the merchant from its float and is repaid at settlement. Native merchants are the escrow receiver themselves and verify Undo calls with the middleware below.

Agents and PayBox

The Undo plugin adds data tools to PayBox. The only approval is PayBox’s own request_wallet_sign, so a protected purchase costs the user one passkey, the same as an unprotected one. Refunds need no approval at all, because money can only move back to the payer.

Tool sequence
undo_quote      { url, method, body?, payer }   → quote, typedData
request_wallet_sign  { op: "typedData", typedData } → signature   (the one passkey)
undo_purchase   { quoteId, signature }         → response, orderId, refundToken
undo_refund     { orderId, refundToken, reason } → refunded or case opened

Agents keep the refundToken from undo_purchase. It is shown once and is the only thing needed to ask for a refund later.

API reference

JSON over HTTPS. Amounts are atomic USDC strings with 6 decimals, addresses are checksummed and times are unix seconds. Errors look like { "error": { "code", "message" } }. The typed client is @undo/sdk.

  • POST/v1/quotes

    Price a purchase and get the typed data to sign.

  • POST/v1/purchases

    Submit the signature. Returns the order, the response and a refund token.

  • GET/v1/orders/:orderId

    One order with its on-chain events.

  • GET/v1/orders?payer=0x…

    Every order of a payer.

  • POST/v1/orders/:orderId/refund

    Ask for a refund with the refund token and a reason.

  • POST/v1/merchants

    Join as a Native merchant, signed by the payout address.

  • GET/v1/merchants/:address

    A merchant's terms and numbers.

  • POST/v1/merchants/:address/cases/:orderId

    Accept or contest a refund request.

  • GET/v1/stats

    Orders protected, volume, refunds and median refund time.

Ask for a refund
curl -X POST "$UNDO_API_URL/v1/orders/ord_7Hq2/refund" \
  -H "content-type: application/json" \
  -d '{ "refundToken": "rt_…", "reason": "Forecast for the wrong city." }'

Native middleware

For a Native merchant, the gateway calls your endpoint with two headers: Undo-Payment, the payment id, and Undo-Signature, an EIP-191 signature by the Undo gateway over undo:v1:<paymentId>:<requestHash>. The middleware checks the signature, recomputes the request hash, and reads the case on-chain to confirm the money is held in escrow for exactly this request.

Hono
import { createOnchainCaseReader, undoHono } from "@undo/sdk/middleware";

const undo = undoHono({
  gateway: "0x0ED3EE2B783Bd1B7752E97e2B8FFF59C05c3a8E9",
  readCase: createOnchainCaseReader({
    rpcUrl: "https://mainnet.base.org",
    operator: "0xA47aa1C3cc3684861E4aC27d5Ff07825C5e76B62",
  }),
});

// Undo calls pass; anything else falls through to x402.
app.use("/paid/*", undo);
Express
import express from "express";
import { createOnchainCaseReader, undoExpress } from "@undo/sdk/middleware";

app.use("/paid", express.raw({ type: "*/*" }), undoExpress({
  gateway: "0x0ED3EE2B783Bd1B7752E97e2B8FFF59C05c3a8E9",
  readCase: createOnchainCaseReader({ rpcUrl: "https://mainnet.base.org", operator: "0xA47aa1C3cc3684861E4aC27d5Ff07825C5e76B62" }),
}));

Refund rules

The gateway refunds automatically when it can see the failure. The reason is recorded on-chain with the refund.

  • Merchant error. The endpoint answered with an error status.
  • Timeout. No answer before the delivery deadline.
  • Empty or malformed. The answer was empty or not what was promised.
  • Duplicate. The same request was charged twice.
  • Instant. The buyer asked and the amount is under the merchant's cap.

Any other refund opens a case. The merchant accepts or contests. Silence counts as acceptance. A contested case goes to an arbiter, who decides within 24 hours and can split the amount. Covered services have a 60 min window; Native merchants choose between 60 min and 7 days.

Contracts

All on Base mainnet. The escrow and collector are Coinbase’s audited contracts, reused as they are.

Beta limits

Undo is in beta. Order sizes and daily volume are capped while the system proves itself on mainnet. Read the beta terms and the security model before relying on it.