Skip to content

Reconciling exposed payments

An exposed reservation is a payment whose signature was transmitted and whose outcome tx402 could not determine — a timeout, a 5xx, a reset, a redirect it declined to follow. tx402 records the exposure as a durable fence before the signature goes on the wire, and once a payment is exposed it never expires: it keeps consuming both the per-hour window and the cumulative ceiling until an operator resolves it. That is deliberate — the conservative direction is to hold budget for a payment that may have settled rather than let a possibly-settled one escape the cap and be spent again. The price of that safety is one manual task: reconciliation.

Automatic reconciliation is 0.3.0. In 0.2.0 it is an operator procedure, and this is it.

An exposed amount shows up in tx402 budget as exposedAtomic, so a non-zero figure there is your signal that reconciliation is due:

Terminal window
tx402 budget api.merchant.example --network eip155:8453
# … "exposedAtomic": "200000", … ← $0.20 is held by a maybe-settled payment

To act on it you enumerate the actual reservations. listExposed is a data-plane read (you do not need to know any reservation ids in advance) that returns every unresolved exposure for a scope and asset:

import { normalizePolicyHost } from "tx402";
const exposed = await store.listExposed({
policyScope: normalizePolicyHost("https://api.merchant.example/…"), // → "api.merchant.example"
assetId: "eip155:8453/erc20:0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
nowEpochMs: Date.now(),
});
// each carries the { reservationId, policyScope, assetId } ref you resolve by

Python is the same call, store.list_exposed(...).

For each exposed reservation, check the chain — the payer address and the merchant’s settlement identifier if you captured one — to establish whether the money actually moved. Then call the admin-plane resolveExposed with your finding:

// it settled — you confirmed the transfer on-chain
await admin.resolveExposed(ref, "committed", Date.now());
// it did not settle — you confirmed no transfer
await admin.resolveExposed(ref, "released", Date.now());
  • committed moves the amount from the exposed counter to cumulative-committed. The net cumulative figure does not change — an exposed amount was already counted — so this is simply the honest record that it did settle, and the per-hour window can now age it out normally.
  • released frees the budget: you are asserting it did not settle, so the reservation should never have held the cap. Use it only when you have confirmed no transfer.

resolveExposed is the one admin method that takes a ReservationRef — the { reservationId, policyScope, assetId } triple listExposed handed you — rather than a scope, because it acts on one specific reservation. Resolving an already-terminal reservation is not a silent no-op — every store refuses it with reservation-already-terminal. A retried reconciliation script must therefore catch that refusal and treat it as “already handled”, rather than assuming the second call quietly did nothing.

Why there is no tx402 resolve-exposed verb

Section titled “Why there is no tx402 resolve-exposed verb”

The five operator verbs deliberately do not include reconciliation, because resolving an exposure is a judgement — you are asserting, per reservation, whether money moved — and that belongs in a reviewed script or an operator console with the chain data in front of it, not a one-line shell command. listExposed → verify → resolveExposed is the loop; a small program that runs it against your gateway or Redis admin store is the tool. tx402 budget is how you know the loop needs running.

Because an exposed amount counts against the cumulative cap the entire time it is unresolved, a fleet can never spend past its ceiling by getting an ambiguous result — the worst case is that budget is held slightly too long, which you clear by reconciling. A store that dropped exposed reservations on a TTL, as v0.1 did, would let a maybe-settled payment escape the ceiling; the non-expiring fence is what closes that, and reconciliation is what releases the budget once you know the truth.