No Facilitator, No Custody, No Fee: How Backendless x402 Works
Most x402 implementations put a service in the middle of every payment. PipRail does not — and the reason it can get away with that is a single design decision: the merchant verifies the payment itself. Here is the architecture that makes backendless possible.
When people picture an x402 payment, they usually picture three parties: a buyer, a seller, and a facilitator in between that settles the transaction. The facilitator is convenient — it abstracts the chain away — but look closely at what it actually is. It holds the funds in transit, which makes it a custodian. It usually takes a cut, which makes it a toll booth. And every payment depends on it being up and honest, which makes it a single point of failure. Three liabilities, bundled into one box that both sides have to trust.
PipRail’s central claim is that the box is unnecessary. None of the work a facilitator does actually requires a middleman to do it. The buyer can broadcast their own transfer. The seller can read that transfer and check it. Remove the box, and the custody, the fee, and the dependency all leave with it.
Verification is local
Here is the part that surprises people: removing the facilitator does not weaken verification, because verification never needed a third party in the first place. A blockchain is already a shared, public source of truth. When a buyer pays, the seller reads the transaction directly from its own node — viem for EVM chains, the native client for each non-EVM family — and confirms it matches the exact payment it asked for. There is no server to run and no database to provision; the gate computes everything it needs from the chain and from the challenge it issued.
This is not PipRail bending the rules. The x402 v2 specification, in §7, explicitly says resource servers may host the verification endpoints themselves rather than handing them to a facilitator. The backendless shape is a configuration the standard anticipates and blesses. We just took it as the default instead of the exception.
The trick that makes proofs unforgeable
A payment proof has to be cryptographically bound to the specific challenge it answers, or it could be replayed or forged. PipRail binds proofs two ways depending on the chain: for some families the challenge nonce rides in a memo or note and verification matches it on the merchant’s own account; for others the proof is the transaction hash itself, verified by reading the transaction plus a recency window and a single-use proof set.
But the load-bearing detail is smaller and easier to miss. Verification always re-derives every field it checks — amount, asset, recipient, network — from the trusted payment requirement the gate issued, never from the client-supplied reference. A malicious client can echo back anything it likes; it does not matter, because verify() never reads its own truth from the echo. A forged or altered proof has nothing legitimate to point at, and replayed proofs are caught by the single-use set. The honesty of the check does not depend on the honesty of the payer.
One contract, ten families, mirrored
Reaching 29 chains without the core turning into a tangle takes discipline, and the discipline has a name: the PaymentDriver abstraction. The protocol layer — the payment gate, the client, the wire envelopes — depends only on that one contract. It imports no chain libraries at all: no viem, no @solana/web3.js, nothing chain-specific. Each chain family is a self-contained driver behind the contract, and the families mirror each other file-for-file.
drivers/
evm/ chains · wallet · pay · verify · index
solana/ chains · wallet · pay · verify · index
near/ chains · wallet · pay · verify · index
stellar/ chains · wallet · pay · verify · index
xrpl/ chains · wallet · pay · verify · index
... (one self-contained folder per chain family)Adding a chain family means implementing the same five files and registering the driver — the core never moves. It also means the non-EVM drivers are optional peers that load only when you name one of their chains, so a pure-EVM install carries none of their weight. Breadth on the outside, a small unchanging contract on the inside.
Replay protection you can make durable
Out of the box, the used-proof set lives in memory, which is exactly right for a single instance. Run several, and you want them to share one view of which proofs have been spent — so the store is pluggable. Hand the gate an isUsed and a markUsed backed by Redis or a database, and every instance reads from the same set. The protocol logic does not change; only where “seen” is recorded does.
requirePayment({
chain: 'base', token: 'USDC', amount: '0.01', payTo,
// Default replay protection is in-memory. Make it durable across instances:
isUsed: (ref) => redis.sIsMember('piprail:proofs', ref),
markUsed: (ref) => redis.sAdd('piprail:proofs', ref),
})Why this is the whole product
It is tempting to add a backend. A backend is where dashboards live, where analytics accrue, where a fee can be inserted. But the moment a payment routes through your server, you are a custodian and a point of failure, and the thing you built stops being a tool the developer owns and starts being a platform they depend on. PipRail’s architecture is a standing refusal to cross that line. No backend, no database, no account, no fee — not because we couldn’t build them, but because the absence is the point. The rail should be something you own, not a platform you join. Verifying your own payments, against your own node, with nobody in the middle, is what owning it looks like.
Like & share PipRail
Star on GitHub Follow @piprailhq