x402 — payment-required over HTTP

Dynamo speaks x402: HTTP 402 with a machine-readable challenge, answered by an X-PAYMENT header. Two schemes ride the same challenge:

against a stream, binds the payment to the seller's request nonce, and presents a consume receipt. Request-bound, replay-rejected, idempotent.

allowance credential; the seller meters actual work against it.

Everything the seller checks is verifiable offline against a pinned engine signer — no payment-network call in the request path.

The full round trip

import { Dynamo, usd, units, x402 } from "@dynamoprotocol/sdk";

const dynamo = await Dynamo.open({
  coreUrl: process.env.DYNAMO_CORE_URL!,
  token: process.env.OWNER_API_TOKEN!,
});
const seller = "0x00000000000000000000000000000000000000D4";
const sleep = (ms: number): Promise<void> => new Promise((r) => setTimeout(r, ms));

// BUYER side: a capped stream aimed at the seller.
const budget = await dynamo.openBudget({ funding: "none", cap: usd("5") });
const stream = await budget.stream({ to: seller, rate: units(1_000_000n), cap: units(2_000_000n) });
await sleep(1_100);

// SELLER side: the 402 challenge for a priced resource.
const challenge = x402.issueChallenge({
  resource: "https://api.example.test/report",
  payTo: seller,
  maxAmountRequired: 250_000n,
  requestRef: "req-docs-1",
});

// BUYER: answer with the exact scheme — ticks the price, binds the receipt.
const answer = await x402.answerChallenge(challenge, stream, { prefer: "exact" });
if (answer.headerName !== "X-PAYMENT") throw new Error("payment rides X-PAYMENT");

// SELLER: verify ENTIRELY offline against the PINNED engine signer — the
// address your budget reported at open; operators pin it in config.
const verified = await x402.verifyPayment(answer.headerValue, {
  expectedSigner: budget.engineSigner as `0x${string}`,
  serviceNode: seller,
  nowSeconds: BigInt(Math.floor(Date.now() / 1000)),
  expectedRequestRef: "req-docs-1",
  requiredUnits: 250_000n,
});
if (!verified.valid) throw new Error(`payment must verify: ${verified.reason}`);
if (verified.scheme !== "exact") throw new Error("expected the exact scheme");

// The allowance is AIMED: a different service node replaying the same
// payment is refused with a named reason — never served.
const replayed = await x402.verifyPayment(answer.headerValue, {
  expectedSigner: budget.engineSigner as `0x${string}`,
  serviceNode: "0x00000000000000000000000000000000000000aa",
  nowSeconds: BigInt(Math.floor(Date.now() / 1000)),
});
if (replayed.valid) throw new Error("a payment aimed at another node must be refused");

await stream.revoke();
await budget.close();
const session = await budget.aggregate();
if (!session.reconciled) throw new Error("engine and settlement layer disagree");

For buyers, x402.x402Fetch(fetch, stream) wraps any fetch: on a 402 it answers the challenge from the stream and retries ONCE; a second 402 propagates — fail closed, no payment loops.

Merchant protection

The seller-side checks are not optional garnish; they are the conformance suite's A.2b section: replay rejection, idempotent consume records, voucher monotonicity, exposure bounds (refuse new unsettled work past a bound — a free pre-check), and finality-aware access for high prices.

The conformance suite

"Conformance-tested" is a specific, runnable claim (R4):

export DYNAMO_CORE_URL=https://your-instance-url   # funds the test streams
export OWNER_API_TOKEN=your-owner-api-token
npx @dynamoprotocol/conformance-kit --target https://your-seller.example

The kit runs the full catalog — challenge shape, both schemes, denial ordering, A.2b merchant protection — against any endpoint that speaks 402 + X-PAYMENT, and writes a machine-readable report with its own digest, so a sign-off can be pinned and re-verified. The gateway plugins pass the identical catalog live.